| • | 目录 |
| 企业集成模式:设计、构建和部署消息传递解决方案 | ||
| 作者: 格雷戈尔·霍普、鲍比·伍尔夫 | ||
| 出版商 | :艾迪生韦斯利 | |
| 发布日期 | : 2003 年 10 月 10 日 | |
| 国际标准书号 | :0-321-20068-3 | |
| 页数 | : 736 | |
您想使用一致的视觉符号来绘制集成解决方案吗?查看前盖内部。
Would you like to use a consistent visual notation for drawing integration solutions? Look inside the front cover.
您想利用异步系统的强大功能而不陷入陷阱吗?请参阅简介中的“异步思考”。
Do you want to harness the power of asynchronous systems without getting caught in the pitfalls? See "Thinking Asynchronously" in the Introduction.
您想知道哪种类型的应用程序集成最适合您的目的吗?请参阅第 2 章,集成样式。
Do you want to know which style of application integration is best for your purposes? See Chapter 2, Integration Styles.
您想学习并发处理消息的技术吗?请参阅第 10 章,竞争消费者和消息调度程序。
Do you want to learn techniques for processing messages concurrently? See Chapter 10, Competing Consumers and Message Dispatcher.
您想了解如何跟踪跨分布式系统流动的异步消息吗?请参阅第 11 章,消息历史记录和消息存储。
Do you want to learn how you can track asynchronous messages as they flow across distributed systems? See Chapter 11, Message History and Message Store.
您是否想了解如何使用 Java Web 服务、.NET 消息队列和基于 TIBCO 的发布-订阅架构来实现使用集成模式设计的系统?请参阅第 9 章,插曲:组合消息。
Do you want to understand how a system designed using integration patterns can be implemented using Java Web services, .NET message queuing, and a TIBCO-based publish-subscribe architecture? See Chapter 9, Interlude: Composed Messaging.
经验丰富的专家 Gregor Hohpe 和 Bobby Woolf 利用多年的实践经验,展示了异步消息传递如何被证明是企业集成成功的最佳策略。然而,构建和部署消息传递解决方案给开发人员带来了许多问题。企业集成模式提供了包含65 种模式的宝贵目录,以及实际的解决方案,这些解决方案展示了消息传递的强大功能,并帮助您为企业设计有效的消息传递解决方案。
Utilizing years of practical experience, seasoned experts Gregor Hohpe and Bobby Woolf show how asynchronous messaging has proven to be the best strategy for enterprise integration success. However, building and deploying messaging solutions presents a number of problems for developers. Enterprise Integration Patterns provides an invaluable catalog of sixty-five patterns, with real-world solutions that demonstrate the formidable of messaging and help you to design effective messaging solutions for your enterprise.
作者还提供了涵盖各种不同集成技术的示例,例如 JMS、MSMQ、TIBCO ActiveEnterprise、Microsoft BizTalk、SOAP 和 XSL。描述债券交易系统的案例研究阐释了实践中的模式,本书介绍了新兴标准,并洞察了企业集成的未来可能会发生什么。
The authors also include examples covering a variety of different integration technologies, such as JMS, MSMQ, TIBCO ActiveEnterprise, Microsoft BizTalk, SOAP, and XSL. A case study describing a bond trading system illustrates the patterns in practice, and the book offers a look at emerging standards, as well as insights into what the future of enterprise integration might hold.
本书提供了一致的词汇和视觉符号框架来描述跨多种技术的大规模集成解决方案。它还详细探讨了异步消息传递架构的优点和局限性。作者提供了有关设计将应用程序连接到消息传递系统的代码的实用建议,并提供了广泛的信息来帮助您确定何时发送消息、如何将其路由到正确的目的地以及如何监视消息传递系统的运行状况。如果您想了解如何管理、监控和维护正在使用的消息系统,请阅读本书。
This book provides a consistent vocabulary and visual notation framework to describe large-scale integration solutions across many technologies. It also explores in detail the advantages and limitations of asynchronous messaging architectures. The authors present practical advice on designing code that connects an application to a messaging system, and provide extensive information to help you determine when to send a message, how to route it to the proper destination, and how to monitor the health of a messaging system. If you want to know how to manage, monitor, and maintain a messaging system once it is in use, get this book.
| • | 目录 |
| 企业集成模式:设计、构建和部署消息传递解决方案 | ||
| 作者: 格雷戈尔·霍普、鲍比·伍尔夫 | ||
| 出版商 | :艾迪生韦斯利 | |
| 发布日期 | : 2003 年 10 月 10 日 | |
| 国际标准书号 | :0-321-20068-3 | |
| 页数 | : 736 | |
制造商和销售商用来区分其产品的许多名称都被称为商标。如果这些名称出现在本书中,并且 Addison-Wesley 知道商标声明,则这些名称均以首字母大写或全部大写印刷。
Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and Addison-Wesley was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals.
作者和出版商在准备本书的过程中非常谨慎,但没有做出任何形式的明示或暗示的保证,并且对错误或遗漏不承担任何责任。对于因使用此处包含的信息或程序而产生的偶然或间接损害,我们不承担任何责任。
The authors and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein.
出版商在批量购买和特价销售时提供本书折扣。获取更多资讯,请联系:
The publisher offers discounts on this book when ordered in quantity for bulk purchases and special sales. For more information, please contact:
美国企业和政府销售
(800) 382-3419
corpsales@pearsontechgroup.com
U.S. Corporate and Government Sale
(800) 382-3419
corpsales@pearsontechgroup.com
对于美国境外的销售,请联系:
For sales outside of the U.S., please contact:
国际销售
(317) 581-3793
International@pearsontechgroup.com
International Sales
(317) 581-3793
international@pearsontechgroup.com
访问 Addison-Wesley 网站:www.awprofessional.com
Visit Addison-Wesley on the Web: www.awprofessional.com
美国国会图书馆出版数据编目
Library of Congress Cataloging-in-Publication Data
霍普,格雷戈尔。
企业集成模式:设计、构建和部署消息传递解决
方案/Gregor Hohpe、Bobby Woolf。
p。厘米。
包括参考书目和索引。
ISBN 0-321-20068-3
1. 电信消息处理。2.管理信息
系统。I.伍尔夫,鲍比。二. 标题。
TK5102.5.H5882 2003
005.7'136dc22
2003017989
Hohpe, Gregor.
Enterprise integration patterns : designing, building, and deploying messaging
solutions / Gregor Hohpe, Bobby Woolf.
p. cm.
Includes bibliographical references and index.
ISBN 0-321-20068-3
1. TelecommunicationMessage processing. 2. Management information
systems. I. Woolf, Bobby. II. Title.
TK5102.5.H5882 2003
005.7'136dc22
2003017989
版权所有 © 2004 培生教育公司
Copyright © 2004 by Pearson Education, Inc.
版权所有。未经出版商事先同意,不得以任何形式或任何方式(电子、机械、复印、录制或其他方式)复制、存储或传播本出版物的任何部分。美国印刷。加拿大同步出版。
All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form, or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior consent of the publisher. Printed in the United States of America. Published simultaneously in Canada.
有关获得使用本作品材料的许可的信息,请向以下地址提交书面请求:
For information on obtaining permission for use of material from this work, please submit a written request to:
Pearson Education, Inc.
权利和合同部 75
Arlington Street, Suite 300
Boston, MA 02116 传真:
(617) 848-7047
Pearson Education, Inc.
Rights and Contracts Department
75 Arlington Street, Suite 300
Boston, MA 02116
Fax: (617) 848-7047
文字印在再生纸上
Text printed on recycled paper
1 2 3 4 5 6 7 8 9 10CRS0706050403
1 2 3 4 5 6 7 8 9 10CRS0706050403
首次印刷,2003 年 10 月
First printing, October 2003
致我的家人和所有在我从书本“紧缩模式”中走出来后仍然记得我的朋友
To my family and all my friends who still remember me after I emerged from book "crunch mode"
格雷戈尔
Gregor
致我的新婚妻子莎伦
To Sharon, my new wife
鲍比
Bobby
艾迪生-韦斯利签名系列为读者提供了有关计算机专业人员现代技术最新趋势的实用且权威的信息。该系列基于一个简单的前提:伟大的书籍来自伟大的作者。该系列书籍均由专家顾问、世界级作者亲自挑选。这些专家很自豪能在封面上签名,他们的签名确保这些思想领袖与作者密切合作,确定主题范围、书籍范围、关键内容和整体独特性。专家签名也象征着对读者的承诺:您正在阅读一部未来的经典。
The Addison-Wesley Signature Series provides readers with practical and authoritative information on the latest trends in modern technology for computer professionals. The series is based on one simple premise: great books come from great authors. Books in the series are personally chosen by expert advisors, world-class authors in their own right. These experts are proud to put their signatures on the covers, and their signatures ensure that these thought leaders have worked closely with authors to define topic coverage, book scope, critical content, and overall uniqueness. The expert signatures also symbolize a promise to our readers: you are reading a future classic.
艾迪生-韦斯利签名系列
The Addison-Wesley Signature Series
签名者:肯特·贝克和马丁·福勒
SIGNERS: KENT BECK AND MARTIN FOWLER
Kent Beck开创了以人为本的技术,例如 JUnit、极限编程和软件开发模式。Kent 感兴趣的是通过寻找一种同时满足经济、美学、情感和实践约束的软件开发风格来帮助团队取得好成绩。他的书重点关注软件创建者和用户的生活。
Kent Beck has pioneered people-oriented technologies like JUnit, Extreme Programming, and patterns for software development. Kent is interested in helping teams do well by doing good finding a style of software development that simultaneously satisfies economic, aesthetic, emotional, and practical constraints. His books focus on touching the lives of the creators and users of software.
Martin Fowler是企业应用程序中对象技术的先驱。他最关心的是如何设计好软件。他专注于深入了解如何构建可持续发展的企业软件的核心。他有兴趣在技术细节背后寻找持续多年的模式、实践和原则;这些书十年后应该可以使用。马丁的标准是这些是他希望自己能写的书。
Martin Fowler has been a pioneer of object technology in enterprise applications. His central concern is how to design software well. He focuses on getting to the heart of how to build enterprise software that will last well into the future. He is interested in looking behind the specifics of technologies to the patterns, practices, and principles that last for many years; these books should be usable a decade from now. Martin's criterion is that these are books he wished he could write.
系列中的标题
Titles in the Series
测试驱动开发:以
Kent,ISBN:0321146530
Test-Driven Development: By Example
Kent Beck, ISBN: 0321146530
企业应用程序架构模式 Martin
Fowler,ISBN:0321127420
Patterns of Enterprise Application Architecture
Martin Fowler, ISBN: 0321127420
超越软件架构:创建和维持获胜解决方案
Luke Hohmann,ISBN:0201775948
Beyond Software Architecture: Creating and Sustaining Winning Solutions
Luke Hohmann, ISBN: 0201775948
企业集成模式:设计、构建和部署消息传递解决方案
Gregor Hohpe 和 Bobby Woolf,ISBN:0321200683
Enterprise Integration Patterns: Designing, Building, and Deploying Messaging Solutions
Gregor Hohpe and Bobby Woolf, ISBN: 0321200683
欲了解更多信息,请访问该系列网站:www.awprofessional.com
For more information, check out the series web site at www.awprofessional.com
作者:约翰·克鲁皮
by John Crupi
当新技术出现时你会做什么?你学习技术。这正是我所做的。我学习了 J2EE(来自 Sun Microsystems,这似乎是合乎逻辑的选择)。具体来说,我通过阅读规范来关注 EJB 技术(因为还没有书籍)。然而,学习技术只是第一步,真正的目标是学习如何有效地应用技术。平台技术的好处在于它们会限制您执行某些任务。但是,就技术而言,你可以为所欲为,但如果做得不当,常常会遇到麻烦。
What do you do when a new technology arrives? You learn the technology. This is exactly what I did. I studied J2EE (being from Sun Microsystems, it seemed to be the logical choice). Specifically, I focused on the EJB technology by reading the specifications (since there were no books yet). Learning the technology, however, is just the first stepthe real goal is to learn how to effectively apply the technology. The nice thing about platform technologies is that they constrain you to performing certain tasks. But, as far as the technology is concerned, you can do whatever you want and quite often get into trouble if you don't do things appropriately.
在过去 15 年里我看到的一件事是,软件开发人员似乎痴迷于两个领域:编程和设计,或者更具体地说,有效地编程和设计。有很多很棒的书籍告诉您用 Java 和 C# 进行某些编程的最有效方法,但告诉您如何有效设计的却少之又少。这就是本书的用武之地。当 Deepak Alur、Dan Malks 和我撰写《核心 J2EE 模式》时,我们希望帮助 J2EE 开发人员“设计”更好的代码。我们做出的最佳决定是使用模式作为选择的工件。正如 Sun 杰出工程师 James Baty 所说,“模式似乎是设计的最佳点。” 我完全同意,幸运的是,格雷戈尔和鲍比也有同样的感觉。
One thing I've seen in the past 15 years is that there seem to be two areas that software developers obsess over: programming and designingor more specifically, programming and designing effectively. There are great books out there that tell you the most efficient way to program certain things in Java and C#, but far fewer tell you how to design effectively. That's where this book comes in. When Deepak Alur, Dan Malks, and I wrote Core J2EE Patterns, we wanted to help J2EE developers "design" better code. The best decision we made was to use patterns as the artifact of choice. As James Baty, a Sun Distinguished Engineer, puts it, "Patterns seem to be the sweet spot of design." I couldn't agree more, and luckily for us, Gregor and Bobby feel the same way.
本书重点关注一个热门且不断发展的主题:使用消息传递进行集成。消息传递不仅是集成的关键,而且很可能成为未来几年 Web 服务的主要焦点。如今,Web 服务世界中存在着如此多的噪音,仅仅确定要关注的规范和技术就是一项微妙而复杂的工作。目标保持不变,但是软件可以帮助您解决问题。正如 J2EE 和 .NET 的早期一样,Web 服务还没有太多设计帮助。许多人说 Web 服务只是解决我们现有集成问题的一种新的开放方式,我同意。但是,这并不意味着我们知道如何设计 Web 服务。这就引出了本书的精华。我相信这本书包含了我们设计 Web 服务和其他集成系统所需的许多模式。由于 Web 服务规范仍在争论中,因此 Bobby 和 Gregor 提供许多 Web 服务规范的示例是没有意义的。但是,没关系。当规范成为标准并且我们使用本书中的模式来设计通过这些标准实现的解决方案时,才会产生真正的回报。那么也许我们可以实现我们的下一个集成目标,即面向服务的架构设计。对于 Bobby 和 Gregor 来说,提供许多 Web 服务规范的示例是没有意义的。但是,没关系。当规范成为标准并且我们使用本书中的模式来设计通过这些标准实现的解决方案时,才会产生真正的回报。那么也许我们可以实现我们的下一个集成目标,即面向服务的架构设计。对于 Bobby 和 Gregor 来说,提供许多 Web 服务规范的示例是没有意义的。但是,没关系。当规范成为标准并且我们使用本书中的模式来设计通过这些标准实现的解决方案时,才会产生真正的回报。那么也许我们可以实现我们的下一个集成目标,即面向服务的架构设计。
This book focuses on a hot and growing topic: integration using messaging. Not only is messaging key to integration, but it will most likely be the predominant focus in Web services for years to come. There is so much noise today in the Web services world, it's a delicate and complex endeavor just to identify the specifications and technologies to focus on. The goal remains the same, howeversoftware helps you solve a problem. Just as in the early days of J2EE and .NET, there is not a lot of design help out there yet for Web services. Many people say Web services is just a new and open way to solve our existing integration problemsand I agree. But, that doesn't mean we know how to design Web services. And that brings us to the gem of this book. I believe this book has many of the patterns we need to design Web services and other integration systems. Because the Web service specifications are still battling it out, it wouldn't have made sense for Bobby and Gregor to provide examples of many of the Web service specifications. But, that's okay. The real payoff will result when the specifications become standards and we use the patterns in this book to design for those solutions that are realized by these standards. Then maybe we can realize our next integration goal of designing for service-oriented architectures.
阅读这本书并将其放在身边。它将让您的软件职业生涯永无止境。
Read this book and keep it by your side. It will enhance your software career to no end.
约翰·克鲁皮·贝塞斯达(John Crupi
Bethesda),医学博士 2003 年
8 月
John Crupi
Bethesda, MD
August 2003
马丁·福勒
by Martin Fowler
当我在写《企业应用程序架构模式》一书时,我很幸运在位于罗利达勒姆的 Kyle 办公室举行的一些非正式研讨会上得到了 Kyle Brown 和 Rachel Reinitz 的深入评论。在这些会议期间,我们意识到我的工作中的一个很大的差距是异步消息系统。
While I was working on my book Patterns of Enterprise Application Architecture, I was lucky to get some in-depth review from Kyle Brown and Rachel Reinitz at some informal workshops at Kyle's office in Raleigh-Durham. During these sessions, we realized that a big gap in my work was asynchronous messaging systems.
我的书中有很多空白,我从来没有打算把它作为企业开发模式的完整集合。但异步消息传递方面的差距尤为重要,因为我们相信异步消息传递将在企业软件开发中发挥越来越重要的作用,特别是在集成方面。集成很重要,因为应用程序不能彼此隔离。我们需要一些技术,使我们能够采用从未设计为互操作的应用程序并打破“烟囱”,这样我们就可以获得比单个应用程序所能提供的更大的好处。
There are many gaps in my book, and I never intended it to be a complete collection of patterns for enterprise development. But the gap on asynchronous messaging is particularly important because we believe that asynchronous messaging will play an increasingly important role in enterprise software development, particularly in integration. Integration is important because applications cannot live isolated from each other. We need techniques that allow us to take applications that were never designed to interoperate and break down the stovepipes so we can gain a greater benefit than the individual applications can offer us.
各种技术都有望解决集成难题。我们都得出结论,消息传递是最有前途的技术。我们面临的挑战是传达如何有效地进行消息传递。其中最大的挑战是消息本质上是异步的,并且您在异步世界中使用的设计方法存在显着差异。
Various technologies have been around that promise to solve the integration puzzle. We all concluded that messaging is the technology that carries the greatest promise. The challenge we faced was to convey how to do messaging effectively. The biggest challenge in this is that messages are by their nature asynchronous, and there are significant differences in the design approaches that you use in an asynchronous world.
我没有空间、精力,或者坦率地说没有知识来在企业应用程序架构模式中正确讨论这个主题。但我们想出了一个更好的解决方案来解决这个问题:找到其他有能力的人。我们追捕了格雷戈尔和鲍比,他们接受了挑战。结果就是您将要阅读的书。
I didn't have space, energy, or frankly the knowledge to cover this topic properly in Patterns of Enterprise Application Architecture. But we came up with a better solution to this gap: find someone else who could. We hunted down Gregor and Bobby, and they took up the challenge. The result is the book you're about to read.
我对他们所做的工作感到高兴。如果您已经使用过消息传递系统,那么本书将系统化您和其他人已经通过艰苦的方式学到的大部分知识。如果您即将使用消息传递系统,那么无论您必须使用哪种消息传递技术,本书都将提供非常宝贵的基础。
I'm delighted with the job that they have done. If you've already worked with messaging systems, this book will systematize much of the knowledge that you and others have already learned the hard way. If you are about to work with messaging systems, this book will provide a foundation that will be invaluable no matter which messaging technology you have to work with.
Martin Fowler
梅尔罗斯,马萨诸塞州 2003 年
8 月
Martin Fowler
Melrose, MA
August 2003
这是一本关于使用消息传递进行企业集成的书。它不记录任何特定的技术或产品。相反,它是为使用各种消息传递产品和技术的开发人员和集成商而设计的,例如
This is a book about enterprise integration using messaging. It does not document any particular technology or product. Rather, it is designed for developers and integrators using a variety of messaging products and technologies, such as
IBM (WebSphere MQ Family)、Microsoft (BizTalk)、TIBCO、WebMethods、SeeBeyond、Vitria 等供应商提供的面向消息的中间件 (MOM) 和 EAI 套件。
Message-oriented middleware (MOM) and EAI suites offered by vendors such as IBM (WebSphere MQ Family), Microsoft (BizTalk), TIBCO, WebMethods, SeeBeyond, Vitria, and others.
Java 消息服务 (JMS) 实现已合并到商业和开源 J2EE 应用程序服务器以及独立产品中。
Java Message Service (JMS) implementations incorporated into commercial and open source J2EE application servers as well as standalone products.
Microsoft 的消息队列 (MSMQ),可通过多个 API 访问,包括 Microsoft .NET 中的 System.Messaging 库。
Microsoft's Message Queuing (MSMQ), accessible through several APIs, including the System.Messaging libraries in Microsoft .NET.
支持异步 Web 服务(例如 WS-ReliableMessaging)和相关 API(例如 Sun Microsystems 的 Java API for XML Messaging (JAXM) 或 Microsoft 的 Web Services Extensions (WSE))的新兴 Web 服务标准。
Emerging Web services standards that support asynchronous Web services (for example, WS-ReliableMessaging) and the associated APIs such as Sun Microsystems' Java API for XML Messaging (JAXM) or Microsoft's Web Services Extensions (WSE).
企业集成不仅仅是创建具有分布式网络的单个应用程序层架构,使单个应用程序能够分布在多台计算机上。分布式应用程序中的一层无法自行运行,而集成应用程序是独立的程序,每个程序都可以自行运行,但通过以松散耦合的方式相互协调来发挥作用。消息传递使多个应用程序能够使用“发送后忘记”的方法通过网络交换数据或命令。这允许呼叫者发送信息并在消息系统传输信息的同时立即继续其他工作。或者,稍后可以通过回调通知调用者结果。异步调用和回调可以使设计比同步方法更复杂,但是异步调用可以重试直到成功,这使得通信更加可靠。异步消息传递还具有其他一些优势,例如请求限制和负载平衡。
Enterprise integration goes beyond creating a single application with a distributed n-tier architecture, which enables a single application to be distributed across several computers. Whereas one tier in a distributed application cannot run by itself, integrated applications are independent programs that can each run by themselves, yet that function by coordinating with each other in a loosely coupled way. Messaging enables multiple applications to exchange data or commands across the network using a "send and forget" approach. This allows the caller to send the information and immediately go on to other work while the information is transmitted by the messaging system. Optionally, the caller can later be notified of the result through a callback. Asynchronous calls and callbacks can make a design more complex than a synchronous approach, but an asynchronous call can be retried until it succeeds, which makes the communication much more reliable. Asynchronous messaging also enables several other advantages, such as throttling of requests and load balancing.
本书旨在帮助应用程序开发人员和系统集成商使用面向消息的集成工具连接应用程序:
This book is designed to help application developers and system integrators connect applications using message-oriented integration tools:
设计和构建需要与其他应用程序集成的复杂企业应用程序的应用程序架构师和开发人员。我们假设您正在使用现代企业应用程序平台(例如 Java 2 平台企业版 (J2EE) 或 Microsoft .NET Framework)开发应用程序。本书将帮助您将应用程序连接到消息传递层并与其他应用程序交换信息。本书重点关注应用程序的集成,而不是构建应用程序;为此,我们向您推荐Martin Fowler 的《企业应用程序架构模式》 。
Application architects and developers who design and build complex enterprise applications that need to integrate with other applications. We assume that you're developing your applications using a modern enterprise application platform such as the Java 2 Platform, Enterprise Edition (J2EE), or the Microsoft .NET Framework. This book will help you connect the application to a messaging layer and exchange information with other applications. This book focuses on the integration of applications, not on building applications; for that, we refer you to Patterns of Enterprise Application Architecture by Martin Fowler.
设计和构建连接打包或自定义应用程序的集成解决方案的集成架构师和开发人员。本组中的大多数读者都具有使用 IBM WebSphere MQ、TIBCO、WebMethods、SeeBeyond 或 Vitria 等众多商业集成工具之一的经验,这些工具结合了本书中介绍的许多模式。本书可帮助您理解基本概念,并使用独立于供应商的词汇做出自信的设计决策。
Integration architects and developers who design and build integration solutions connecting packaged or custom applications. Most readers in this group will have experience with one of the many commercial integration tools like IBM WebSphere MQ, TIBCO, WebMethods, SeeBeyond, or Vitria, which incorporate many of the patterns presented in this book. This book helps you understand the underlying concepts and make confident design decisions using a vendor-independent vocabulary.
企业架构师必须维护企业中软件和硬件资产的“全局”视图。本书提供了一致的词汇和图形符号来描述可能跨越多种技术或单点解决方案的大规模集成解决方案。这种语言也是企业架构师与集成和应用程序架构师和开发人员之间有效沟通的关键推动者。
Enterprise architects who have to maintain the "big picture" view of the software and hardware assets in an enterprise. This book presents a consistent vocabulary and graphical notation to describe large-scale integration solutions that may span many technologies or point solutions. This language is also a key enabler for efficient communication between the enterprise architect and the integration and application architects and developers.
本书并不试图为企业应用程序集成提供商业案例;而是试图为企业应用程序集成提供商业案例。重点是如何使其发挥作用。您将通过了解以下内容来学习如何集成企业应用程序:
This book does not attempt to make a business case for enterprise application integration; the focus is on how to make it work. You will learn how to integrate enterprise applications by understanding the following:
与其他集成技术相比,异步消息传递的优点和局限性。
The advantages and limitations of asynchronous messaging as compared to other integration techniques.
如何确定应用程序需要的消息通道,如何控制多个消费者是否可以接收同一消息,以及如何处理无效消息。
How to determine the message channels your applications will need, how to control whether multiple consumers can receive the same message, and how to handle invalid messages.
何时发送消息、消息应包含什么内容以及如何使用特殊消息属性。
When to send a message, what it should contain, and how to use special message properties.
即使发送者不知道最终目的地在哪里,如何将消息路由到最终目的地。
How to route a message to its ultimate destination even when the sender does not know where that is.
当发送者和接收者不同意通用格式时如何转换消息。
How to convert messages when the sender and receiver do not agree on a common format.
如何设计将应用程序连接到消息传递系统的代码。
How to design the code that connects an application to the messaging system.
作为企业的一部分使用后如何管理和监控消息传递系统。
How to manage and monitor a messaging system once it's in use as part of the enterprise.
我们认为,任何标题中带有“企业”一词的书都可能属于以下三类之一。首先,本书可能试图涵盖主题的全部内容,但被迫停止提供有关如何实施实际解决方案的详细指导。其次,这本书可能会提供有关开发实际解决方案的具体实践指导,但被迫限制其所涉及的主题领域的范围。第三,这本书可能试图做到这两点,但很可能永远不会完成,否则出版得太晚,以至于变得无关紧要。我们选择了第二个选择,并希望创建一本能够帮助人们创建更好的集成解决方案的书,尽管我们不得不限制这本书的范围。我们很想讨论但为了不落入第三类陷阱而必须排除的主题包括安全性、复杂数据映射、工作流、规则引擎、可扩展性和鲁棒性以及分布式事务处理(XA、Tuxedo 和喜欢)。我们选择异步消息传递作为本书的重点,因为它充满了有趣的设计问题和权衡,并从各个集成供应商提供的许多实现中提供了清晰的抽象。
We believe that any book sporting the word "enterprise" in the title is likely to fall into one of three categories. First, the book might attempt to cover the whole breadth of the subject matter but is forced to stop short of detailed guidance on how to implement actual solutions. Second, the book might provide specific hands-on guidance on the development of actual solutions but is forced to constrain the scope of the subject area it addresses. Third, the book might attempt to do both but is likely never to be finished or else to be published so late as to be irrelevant. We opted for the second choice and hopefully created a book that helps people create better integration solutions even though we had to limit the scope of the book. Topics that we would have loved to discuss but had to exclude in order not to fall into the category-three trap include security, complex data mapping, workflow, rule engines, scalability and robustness, and distributed transaction processing (XA, Tuxedo, and the like). We chose asynchronous messaging as the emphasis for this book because it is full of interesting design issues and trade-offs, and provides a clean abstraction from the many implementations provided by various integration vendors.
本书也不是关于特定消息传递或中间件技术的教程。为了强调本书中提出的概念的广泛适用性,我们提供了基于许多不同技术的示例,例如 JMS、MSMQ、TIBCO、BizTalk 和 XSL。然而,我们关注的是设计决策和权衡,而不是工具的细节。如果您有兴趣了解有关这些特定技术的更多信息,请参阅参考书目中引用的书籍之一或众多在线资源之一。
This book is also not a tutorial on a specific messaging or middleware technology. To highlight the wide applicability of the concepts presented in this book, we included examples based on a number of different technologies, such as JMS, MSMQ, TIBCO, BizTalk, and XSL. However, we focus on the design decisions and trade-offs as opposed to the specifics of the tool. If you are interested in learning more about any of these specific technologies, please refer to one of the books referenced in the bibliography or to one of the many online resources.
正如标题所示,本书的大部分内容由模式集合组成。在没有简单的“一刀切”答案的领域,例如应用程序体系结构、面向对象的设计或基于异步消息传递体系结构的集成解决方案,模式是一种行之有效的捕获专家知识的方法。
As the title suggests, the majority of this book consists of a collection of patterns. Patterns are a proven way to capture experts' knowledge in fields where there are no simple "one size fits all" answers, such as application architecture, object-oriented design, or integration solutions based on asynchronous messaging architectures.
每个模式都会提出一个特定的设计问题,讨论围绕该问题的考虑因素,并提出一个平衡各种力量或驱动因素的优雅解决方案。在大多数情况下,解决方案并不是首先想到的方法,而是随着时间的推移在实际使用中不断演变的方法。因此,每种模式都包含了高级集成开发人员和架构师通过反复构建解决方案并从错误中学习而获得的经验基础。这意味着我们并没有“发明”本书中的模式;而是我们“发明”了本书中的模式。模式不是发明的,而是在该领域的实际实践中发现和观察到的。
Each pattern poses a specific design problem, discusses the considerations surrounding the problem, and presents an elegant solution that balances the various forces or drivers. In most cases, the solution is not the first approach that comes to mind, but one that has evolved through actual use over time. As a result, each pattern incorporates the experience base that senior integration developers and architects have gained by repeatedly building solutions and learning from their mistakes. This implies that we did not "invent" the patterns in this book; patterns are not invented, but rather discovered and observed from actual practice in the field.
因为模式是从实践者的实际使用中收获的,所以如果您已经使用企业集成工具和异步消息传递架构有一段时间了,那么本书中的许多模式您可能会感到熟悉。然而,即使您已经认识了其中的大部分模式,回顾本书仍然有价值。本书应该验证您对如何使用消息传递来之不易的理解,同时记录您可能不知道的解决方案及其之间关系的详细信息。它还为您提供综合参考,帮助您有效地将知识传授给经验不足的同事。最后,
Because patterns are harvested from practitioners' actual use, chances are that if you have been working with enterprise integration tools and asynchronous messaging architectures for some time, many of the patterns in this book will seem familiar to you. Yet, even if you already recognize most of these patterns, there is still value in reviewing this book. This book should validate your hard-earned understanding of how to use messaging while documenting details of the solutions and relationships between them of which you might not have been aware. It also gives you a consolidated reference to help you pass your knowledge effectively to less-experienced colleagues. Finally, the pattern names give you a common vocabulary to efficiently discuss integration design alternatives with your peers.
本书中的模式适用于各种编程语言和平台。这意味着模式不是剪切和粘贴的代码片段,但您必须针对特定环境实现模式。为了使这种转换更容易,我们添加了各种示例,这些示例展示了使用流行技术(例如 JMS、MSMQ、TIBCO、BizTalk、XSL 等)实现模式的不同方法。我们还提供了一些较大的示例来演示多个模式如何一起发挥作用以形成一个有凝聚力的解决方案。
The patterns in this book apply to a variety of programming languages and platforms. This means that a pattern is not a cut-and-paste snippet of code, but you have to realize a pattern to your specific environment. To make this translation easier, we added a variety of examples that show different ways of implementing patterns using popular technologies such as JMS, MSMQ, TIBCO, BizTalk, XSL, and others. We also included a few larger examples to demonstrate how multiple patterns play together to form a cohesive solution.
使用异步消息传递架构集成多个应用程序是一个充满挑战且有趣的领域。我们希望您能像我们编写这本书一样喜欢阅读这本书。
Integrating multiple applications using an asynchronous messaging architecture is a challenging and interesting field. We hope you enjoy reading this book as much as we did writing it.
马丁·福勒签名系列书籍的共同主题是桥梁的图片。从某种意义上说,我们很幸运,因为什么主题更适合一本关于集成的书呢?数千年来,桥梁帮助连接来自不同海岸、山区和路边的人们。
The common theme for books in the Martin Fowler Signature Series is a picture of a bridge. In some sense we lucked out, because what theme would make a better match for a book on integration? For thousands of years, bridges have helped connect people from different shores, mountains, and sides of the road.
我们选择了日本大阪住吉大社的太鼓桥的照片,因为它简单而优雅。作为供奉水手守护神的神社,最初建在水边。有趣的是,填海造地已将水推开,因此今天的神社矗立在内陆近三英里处。新年伊始,约有三百万人参拜这座神社。
We selected a picture of the Taiko-bashi Bridge at the Sumiyoshi-taisha Shrine in Osaka, Japan, for its simple elegance and beauty. As a Shinto shrine dedicated to the guardian deity for sailors, it was originally erected next to the water. Interestingly, land reclamation has pushed the water away so that the shrine today stands almost three miles inland. Some three million people visit this shrine at the beginning of a new year.
Gregor Hohpe
加利福尼亚
州
旧金山 Bobby Woolf 北卡罗来纳州
罗利2003 年 9 月
www.enterpriseintegrationpatterns.com
Gregor Hohpe
San Francisco, California
Bobby Woolf
Raleigh, North Carolina
September 2003
www.enterpriseintegrationpatterns.com
卡尔·萨根博士的先锋牌匾
The Pioneer Plaque by Dr. Carl Sagan
给外星生命形式的信息。
A message to extraterrestrial life forms.
与大多数书籍一样,《企业集成模式》已经酝酿了很长时间。撰写基于消息的集成模式的想法可以追溯到 2001 年夏天,当时 Martin Fowler 正在研究企业应用程序架构模式( EAA 的 P) 。当时,凯尔·布朗 (Kyle Brown) 突然意识到,虽然EAA 的 P讨论了很多如何创建应用程序,仅简单介绍了如何集成它们。这个想法是马丁和凯尔之间一系列会议的起点,其中包括雷切尔·雷尼茨、约翰·克鲁皮和马克·韦策尔。Bobby 于 2001 年秋季加入了这些讨论,Gregor 于 2002 年初加入。第二年夏天,该小组提交了两篇论文供程序模式语言 (PLoP) 会议审查,其中一篇由 Bobby 和 Kyle 共同撰写,另一篇由 Gregor 撰写。会议结束后,凯尔和马丁重新专注于他们自己的书籍项目,而格雷戈尔和鲍比合并了他们的论文,形成了这本书的基础。同时,www.enterpriseintegrationpatterns.com网站上线后,世界各地的集成架构师和开发人员都可以参与内容的快速发展。在写这本书的过程中,格雷戈尔和鲍比邀请了贡献者参与这本书的创作。凯尔提出最初的想法大约两年后,最终的手稿到达了出版商。
Like most books, Enterprise Integration Patterns has been a long time in the making. The idea of writing about message-based integration patterns dates back to the summer of 2001 when Martin Fowler was working on Patterns of Enterprise Application Architecture (P of EAA). At that time, it struck Kyle Brown that while P of EAA talked a lot about how to create applications, it touches only briefly on how to integrate them. This idea was the starting point for a series of meetings between Martin and Kyle that also included Rachel Reinitz, John Crupi, and Mark Weitzel. Bobby joined these discussions in the fall of 2001, followed by Gregor in early 2002. The following summer the group submitted two papers for review at the Pattern Languages of Programs (PLoP) conference, one authored jointly by Bobby and Kyle and the other by Gregor. After the conference, Kyle and Martin refocused on their own book projects while Gregor and Bobby merged their papers to form the basis for the book. At the same time, the www.enterpriseintegrationpatterns.com site went live to allow integration architects and developers around the world to participate in the rapid evolution of the content. As they worked on the book, Gregor and Bobby invited contributors to participate in the creation of the book. About two years after Kyle's original idea, the final manuscript arrived at the publisher.
本书是社区众多人员共同努力的成果。许多同事和朋友(其中许多人是我们在本书中认识的)提供了示例想法,确保了技术内容的正确性,并给了我们急需的反馈和批评。他们的意见极大地影响了本书的最终形式和内容。我们很高兴承认他们的贡献并对他们的努力表示赞赏。
This book is the result of a community effort involving a great number of people. Many colleagues and friends (many of whom we met through the book effort) provided ideas for examples, ensured the correctness of the technical content, and gave us much needed feedback and criticism. Their input has greatly influenced the final form and content of the book. It is a pleasure for us to acknowledge their contributions and express our appreciation for their efforts.
凯尔·布朗和马丁·福勒为本书奠定了基础,值得特别提及。如果不是 Martin 撰写了EAA 的 P,并且 Kyle 组建了一个小组来讨论消息传递模式以补充 Martin 的书,这本书可能永远不会被写出来。
Kyle Brown and Martin Fowler deserve special mention for laying the foundation for this book. This book might have never been written were it not for Martin's writing P of EAA and Kyle's forming a group to discuss messaging patterns to complement Martin's book.
我们很幸运有几位贡献者撰写了本书的重要部分:Conrad F. D'Cruz、Sean Neville、Michael J. Rettig 和 Jonathan Simon。他们的章节对这些模式如何在实践中发挥作用提供了额外的视角,从而使本书更加完整。
We were fortunate to have several contributors who authored significant portions of the book: Conrad F. D'Cruz, Sean Neville, Michael J. Rettig, and Jonathan Simon. Their chapters round out the book with additional perspectives on how the patterns work in practice.
PLoP 2002 会议上我们的作家研讨会参与者是第一批对材料提供实质性反馈的人,帮助我们朝着正确的方向前进:Ali Arsanjani、Kyle Brown、John Crupi、Eric Evans、Martin Fowler、Brian Marick、托比·萨弗、乔纳森·西蒙、比尔·特鲁德尔和马雷克·沃卡奇。
Our writers' workshop participants at the PLoP 2002 conference were the first people to provide substantial feedback on the material, helping to get us going in the right direction: Ali Arsanjani, Kyle Brown, John Crupi, Eric Evans, Martin Fowler, Brian Marick, Toby Sarver, Jonathan Simon, Bill Trudell, and Marek Vokac.
我们要感谢我们的审稿团队,他们花时间阅读了草稿材料,并向我们提供了宝贵的反馈和建议:
We would like to thank our team of reviewers who took the time to read through the draft material and provided us with invaluable feedback and suggestions:
理查德·赫尔姆
卢克·霍曼
德拉戈斯·马诺莱斯库
大卫·赖斯
Russ Rufer 和硅谷模式小组
马修·肖特
Richard Helm
Luke Hohmann
Dragos Manolescu
David Rice
Russ Rufer and the Silicon Valley Patterns Group
Matthew Short
特别感谢 Russ 在硅谷模式小组中参与起草本书草稿。我们要感谢以下成员的努力:Robert Benson、Tracy Bialik、Jeffrey Blake、Azad Bolour、John Brewer、Bob Evans、Andy Farlie、Jeff Glaza、Phil Goodwin、Alan Harriman、Ken Hejmanowski、Deborah Kaddah、Rituraj Kirti 、Jan Looney、Chris Lopez、Jerry Louis、马道洪、Jeff Miller、Stilian Pandev、John Parello、Hema Pillay、Russ Rufer、Rich Smith、Carol Thistlethwaite、Debbie Utley、Walter Vannini、David Vydra 和 Ted Young。
Special thanks go to Russ for workshopping the book draft in the Silicon Valley Patterns Group. We would like to thank the following members for their efforts: Robert Benson, Tracy Bialik, Jeffrey Blake, Azad Bolour, John Brewer, Bob Evans, Andy Farlie, Jeff Glaza, Phil Goodwin, Alan Harriman, Ken Hejmanowski, Deborah Kaddah, Rituraj Kirti, Jan Looney, Chris Lopez, Jerry Louis, Tao-hung Ma, Jeff Miller, Stilian Pandev, John Parello, Hema Pillay, Russ Rufer, Rich Smith, Carol Thistlethwaite, Debbie Utley, Walter Vannini, David Vydra, and Ted Young.
我们的公共电子邮件讨论列表允许在www.enterpriseintegrationpatterns.com上发现材料的人们插话并分享他们的想法和想法。Bill Trudell 作为邮件列表最活跃的贡献者获得了特殊荣誉。其他活跃海报包括 Venkateshwar Bommineni、Duncan Cragg、John Crupi、Fokko Degenaar、Shailesh Gosavi、Christian Hall、Ralph Johnson、Paul Julius、Orjan Lundberg、Dragos Manolescu、Rob Mee、Srikanth Narasimhan、Sean Neville、Rob Patton、Kirk Pepperdine、Matthew普赖尔、索米克·拉哈、迈克尔·雷蒂格、弗兰克·绍尔、乔纳森·西蒙、费德里科·斯皮纳齐、兰迪·斯塔福德、马雷克·沃卡奇、乔·沃尔恩斯和马克·韦策尔。
Our public e-mail discussion list allowed people who discovered the material on www.enterpriseintegrationpatterns.com to chime in and share their thoughts and ideas. Special honors go to Bill Trudell as the most active contributor to the mailing list. Other active posters included Venkateshwar Bommineni, Duncan Cragg, John Crupi, Fokko Degenaar, Shailesh Gosavi, Christian Hall, Ralph Johnson, Paul Julius, Orjan Lundberg, Dragos Manolescu, Rob Mee, Srikanth Narasimhan, Sean Neville, Rob Patton, Kirk Pepperdine, Matthew Pryor, Somik Raha, Michael Rettig, Frank Sauer, Jonathan Simon, Federico Spinazzi, Randy Stafford, Marek Vokac, Joe Walnes, and Mark Weitzel.
我们感谢马丁·福勒 (Martin Fowler) 招待我们观看他的签名系列节目。马丁的认可给了我们完成这项工作所需的信心和精力。
We thank Martin Fowler for hosting us in his signature series. Martin's endorsement gave us confidence and the energy required to complete this work.
我们感谢约翰·克鲁皮为我们的书撰写前言。他从一开始就观察了这本书的形成,并一直耐心指导,但从未失去幽默感。
We thank John Crupi for writing the foreword for our book. He has observed the book's formation from the beginning and has been a patient guide all along without ever losing his sense of humor.
最后,我们非常感谢 Addison-Wesley 的编辑和制作团队,他们由我们的主编辑 Mike Hendrickson 领导,包括我们的制作协调员 Amy Fleischer;我们的项目经理 Kim Arney Mulcahy;我们的文案编辑 Carol J. Lallier;我们的校对员 Rebecca Rider;我们的索引员 Sharon Hilgenberg;以及杰奎琳·杜赛特、约翰·富勒和伯纳德·加夫尼。
Finally, we owe a great deal to the editing and production team at Addison-Wesley, led by our chief editor, Mike Hendrickson, and including our production coordinator, Amy Fleischer; our project manager, Kim Arney Mulcahy; our copyeditor, Carol J. Lallier; our proofreader, Rebecca Rider; our indexer, Sharon Hilgenberg; as well as Jacquelyn Doucette, John Fuller, and Bernard Gaffney.
我们可能遗漏了一些名字,并且没有给予每个人应有的荣誉,我们深表歉意。但对于所有列出的和未列出的帮助使本书变得更好的人,感谢你们的帮助。我们希望您能像我们一样为这本书感到自豪。
We've likely missed some names and not given everyone the credit they deserve, and we apologize. But to everyone listed and not listed who helped make this book better, thank you for all your help. We hope you can be as proud of this book as we are.
有趣的应用程序很少是孤立存在的。无论您的销售应用程序必须与库存应用程序交互,您的采购应用程序必须连接到拍卖站点,还是您的 PDA 日历必须与公司日历服务器同步,似乎任何应用程序都可以通过与其他应用程序集成来变得更好。
Interesting applications rarely live in isolation. Whether your sales application must interface with your inventory application, your procurement application must connect to an auction site, or your PDA's calendar must synchronize with the corporate calendar server, it seems that any application can be made better by integrating it with other applications.
所有集成解决方案都必须应对一些基本挑战:
All integration solutions have to deal with a few fundamental challenges:
网络不可靠。集成解决方案必须通过网络将数据从一台计算机传输到另一台计算机。与在单台计算机上运行的进程相比,分布式计算必须准备好处理更多的可能问题。通常,要集成的两个系统相隔大洲,它们之间的数据必须通过电话线、LAN 网段、路由器、交换机、公共网络和卫星链路传输。每个步骤都可能导致延迟或中断。
Networks are unreliable. Integration solutions have to transport data from one computer to another across networks. Compared to a process running on a single computer, distributed computing has to be prepared to deal with a much larger set of possible problems. Often, two systems to be integrated are separated by continents, and data between them has to travel through phone lines, LAN segments, routers, switches, public networks, and satellite links. Each step can cause delays or interruptions.
网络很慢。通过网络发送数据比进行本地方法调用慢多个数量级。以与处理单个应用程序相同的方式设计广泛分布的解决方案可能会产生灾难性的性能影响。
Networks are slow. Sending data across a network is multiple orders of magnitude slower than making a local method call. Designing a widely distributed solution the same way you would approach a single application could have disastrous performance implications.
任何两个应用程序都是不同的。集成解决方案需要在使用不同编程语言、操作平台和数据格式的系统之间传输信息。集成解决方案必须能够与所有这些不同的技术交互。
Any two applications are different. Integration solutions need to transmit information between systems that use different programming languages, operating platforms, and data formats. An integration solution must be able to interface with all these different technologies.
改变是不可避免的。应用程序会随着时间的推移而变化。集成解决方案必须跟上所连接的应用程序的变化。集成解决方案很容易陷入变化的雪崩效应中,如果一个系统发生变化,所有其他系统都可能受到影响。集成解决方案需要通过在应用程序之间使用松散耦合来最大限度地减少从一个系统到另一个系统的依赖性。
Change is inevitable. Applications change over time. An integration solution has to keep pace with changes in the applications it connects. Integration solutions can easily get caught in an avalanche effect of changesif one system changes, all other systems may be affected. An integration solution needs to minimize the dependencies from one system to another by using loose coupling between applications.
随着时间的推移,开发人员通过四种主要方法克服了这些挑战:
Over time, developers have overcome these challenges with four main approaches:
文件传输 一个应用程序需要就文件名和位置、文件格式、写入和读取时间以及谁将删除文件达成一致。
File Transfer One application writes a file that another later reads. The applications need to agree on the filename and location, the format of the file, the timing of when it will be written and read, and who will delete the file.
共享数据库 多个由于不存在重复的数据存储,因此无需将数据从一个应用程序传输到另一应用程序。
Shared Database Multiple applications share the same database schema, located in a single physical database. Because there is no duplicate data storage, no data has to be transferred from one application to the other.
远程过程调用 一个应用程序公开其某些功能,以便其他应用程序可以将其作为远程过程进行远程访问。通信是实时同步发生的。
Remote Procedure Invocation One application exposes some of its functionality so that it can be accessed remotely by other applications as a remote procedure. The communication occurs in real time and synchronously.
消息传递 一个其他应用程序可以稍后从通道读取消息。应用程序必须就通道以及消息格式达成一致。通信是异步的。
Messaging One application publishes a message to a common message channel. Other applications can read the message from the channel at a later time. The applications must agree on a channel as well as on the format of the message. The communication is asynchronous.
虽然所有四种方法基本上解决相同的问题,但每种风格都有其独特的优点和缺点。事实上,应用程序可以使用多种样式进行集成,以便每个集成点都可以利用最适合它的样式。
While all four approaches solve essentially the same problem, each style has its distinct advantages and disadvantages. In fact, applications may integrate using multiple styles such that each point of integration takes advantage of the style that suits it best.
本书讲述如何使用消息传递来集成应用程序。了解消息传递功能的一个简单方法是考虑电话系统。电话呼叫是一种同步通信形式。仅当我拨打电话时对方有空时,我才能与对方通信。另一方面,语音邮件允许异步通信。使用语音信箱,当接收者没有接听时,呼叫者可以给他留言;之后,接收者(在他方便的时候)可以收听在他的邮箱中排队的消息。语音邮件使呼叫者现在可以留言,以便接收者稍后可以收听,这比尝试让呼叫者和接收者同时通话要容易得多。语音邮件将(至少部分)电话捆绑到一条消息中,并将其排队以供以后使用;这本质上就是消息传递的工作原理。
This book is about how to use messaging to integrate applications. A simple way to understand what messaging does is to consider the telephone system. A telephone call is a synchronous form of communication. I can communicate with the other party only if the other party is available at the time I place the call. Voice mail, on the other hand, allows asynchronous communication. With voice mail, when the receiver does not answer, the caller can leave him a message; later, the receiver (at his convenience) can listen to the messages queued in his mailbox. Voice mail enables the caller to leave a message now so that the receiver can listen to it later, which is much easier than trying to get the caller and the receiver on the phone at the same time. Voice mail bundles (at least part of) a phone call into a message and queues it for later consumption; this is essentially how messaging works.
消息传递是一种能够实现高速、异步、程序到程序通信并可靠交付的技术。程序通过向彼此发送称为消息的数据包来进行通信。Channel ,也称为队列,是连接程序和传递消息的逻辑路径。通道的行为类似于消息的集合或数组,但可以在多台计算机之间神奇地共享,并且可以由多个应用程序同时使用。发送者或生产者是通过将消息写入通道来发送消息的程序。接收者或消费者是一个通过从通道读取(并删除)消息来接收消息的程序。
Messaging is a technology that enables high-speed, asynchronous, program-to-program communication with reliable delivery. Programs communicate by sending packets of data called messages to each other. Channels, also known as queues, are logical pathways that connect the programs and convey messages. A channel behaves like a collection or array of messages, but one that is magically shared across multiple computers and can be used concurrently by multiple applications. A sender or producer is a program that sends a message by writing the message to a channel. A receiver or consumer is a program that receives a message by reading (and deleting) it from a channel.
消息本身只是某种数据结构,例如字符串、字节数组、记录或对象。它可以简单地解释为数据、接收方调用的命令的描述或发送方发生的事件的描述。消息实际上包含两部分,标题和正文。标头包含有关消息发送者、消息去向等的元信息;该信息由消息传递系统使用,并且大部分被使用消息的应用程序忽略。身体_包含正在传输的应用程序数据,通常会被消息传递系统忽略。在对话中,当使用消息传递的应用程序开发人员谈论消息时,她通常指的是消息正文中的数据。
The message itself is simply some sort of data structuresuch as a string, a byte array, a record, or an object. It can be interpreted simply as data, as the description of a command to be invoked on the receiver, or as the description of an event that occurred in the sender. A message actually contains two parts, a header and a body. The header contains meta-information about the messagewho sent it, where it's going, and so on; this information is used by the messaging system and is mostly ignored by the applications using the messages. The body contains the application data being transmitted and is usually ignored by the messaging system. In conversation, when an application developer who is using messaging talks about a message, she's usually referring to the data in the body of the message.
异步消息架构很强大,但需要我们重新思考我们的开发方法。与其他三种集成方法相比,接触过消息传递和消息系统的开发人员相对较少。因此,应用程序开发人员一般不太熟悉该通信平台的习惯用法和特性。
Asynchronous messaging architectures are powerful but require us to rethink our development approach. As compared to the other three integration approaches, relatively few developers have had exposure to messaging and message systems. As a result, application developers in general are not as familiar with the idioms and peculiarities of this communications platform.
消息传递功能通常由称为消息传递系统或面向消息的中间件的单独软件系统提供(妈妈)。消息传递系统管理消息传递的方式与数据库系统管理数据持久性的方式相同。正如管理员必须使用应用程序数据的架构填充数据库一样,管理员必须使用定义应用程序之间通信路径的通道来配置消息传递系统。然后消息系统协调和管理消息的发送和接收。数据库系统的主要目的是确保每个数据记录都安全地保存,同样,消息传递系统的主要任务是以可靠的方式将消息从发送者的计算机移动到接收者的计算机。
Messaging capabilities are typically provided by a separate software system called a messaging system or message-oriented middleware (MOM). A messaging system manages messaging the way a database system manages data persistence. Just as an administrator must populate the database with the schema for an application's data, an administrator must configure the messaging system with the channels that define the paths of communication between the applications. The messaging system then coordinates and manages the sending and receiving of messages. The primary purpose of a database system is to make sure each data record is safely persisted, and likewise the main task of a messaging system is to move messages from the sender's computer to the receiver's computer in a reliable fashion.
需要消息系统将消息从一台计算机移动到另一台计算机,因为计算机和连接它们的网络本质上是不可靠的。仅仅因为一个应用程序准备好发送数据并不意味着另一应用程序准备好接收数据。即使两个应用程序均已准备就绪,网络也可能无法正常工作或无法正确传输数据。消息传递系统通过反复尝试传输消息直到成功来克服这些限制。理想情况下,消息一次就能成功传输,但情况往往并不理想。
A messaging system is needed to move messages from one computer to another because computers and the networks that connect them are inherently unreliable. Just because one application is ready to send data does not mean that the other application is ready to receive it. Even if both applications are ready, the network may not be working or may fail to transmit the data properly. A messaging system overcomes these limitations by repeatedly trying to transmit the message until it succeeds. Under ideal circumstances, the message is transmitted successfully on the first try, but circumstances are often not ideal.
本质上,消息的传输分为五个步骤:
In essence, a message is transmitted in five steps:
创建 发送者创建消息并用数据填充它。
Create The sender creates the message and populates it with data.
发送 发送者将消息添加到频道。
Send The sender adds the message to a channel.
传递消息传递系统将消息从发送者的计算机移动到接收者的计算机,使接收者可以使用该消息。
Deliver The messaging system moves the message from the sender's computer to the receiver's computer, making it available to the receiver.
接收接收者从通道读取消息。
Receive The receiver reads the message from the channel.
过程接收方从消息中提取数据。
Process The receiver extracts the data from the message.
下图说明了这五个传输步骤,每个步骤由计算机执行,以及哪些步骤涉及消息传递系统:
The following figure illustrates these five transmission steps, which computer performs each, and which steps involve the messaging system:
消息传输步骤
Message Transmission Step-by-Step
该图还说明了两个重要的消息传递概念:
This figure also illustrates two important messaging concepts:
发送后忘记 在步骤 2 中,发送应用程序将消息发送到消息通道。发送完成后,发送者可以继续其他工作,同时消息传递系统在后台传输消息。发送者可以确信接收者最终会收到消息,而不必等到这种情况发生。
Send and forget In step 2, the sending application sends the message to the message channel. Once that send is complete, the sender can go on to other work while the messaging system transmits the message in the background. The sender can be confident that the receiver will eventually receive the message and does not have to wait until that happens.
存储和转发 在 步骤 2 中,当发送应用程序将消息发送到消息通道时,消息传递系统将消息存储在发送者的计算机上(内存或磁盘中)。在步骤3中,消息传送系统通过将消息从发送者的计算机转发到接收者的计算机来传递消息,然后再次将消息存储在接收者的计算机上。当消息从一台计算机移动到另一台计算机时,这种存储转发过程可能会重复多次,直到到达接收者的计算机。
Store and forward In step 2, when the sending application sends the message to the message channel, the messaging system stores the message on the sender's computer, either in memory or on disk. In step 3, the messaging system delivers the message by forwarding it from the sender's computer to the receiver's computer, and then stores the message once again on the receiver's computer. This store-and-forward process may be repeated many times as the message is moved from one computer to another until it reaches the receiver's computer.
创建、发送、接收和处理步骤可能看起来像是不必要的开销。为什么不简单地将数据传递给接收者呢?通过将数据包装为消息并将其存储在消息传递系统中,应用程序将传递数据的责任委托给消息传递系统。由于数据被包装为原子消息,因此可以重试传递直至成功,并且可以确保接收者可靠地接收到数据的一份副本。
The create, send, receive, and process steps may seem like unnecessary overhead. Why not simply deliver the data to the receiver? By wrapping the data as a message and storing it in the messaging system, the applications delegate to the messaging system the responsibility of delivering the data. Because the data is wrapped as an atomic message, delivery can be retried until it succeeds, and the receiver can be assured of reliably receiving exactly one copy of the data.
现在我们知道什么是消息传递,我们应该问,为什么要使用消息传递?与任何复杂的解决方案一样,没有一个简单的答案。简单的回答是,消息传递比文件传输更直接,比共享,并且比远程。然而,这只是使用消息传递可以获得的优势的开始。
Now that we know what messaging is, we should ask, Why use messaging? As with any sophisticated solution, there is no one simple answer. The quick answer is that messaging is more immediate than File Transfer, better encapsulated than Shared Database, and more reliable than Remote Procedure Invocation. However, that's just the beginning of the advantages that can be gained using messaging.
消息传递的具体好处包括:
Specific benefits of messaging include:
远程通讯。消息传递使单独的应用程序能够进行通信和传输数据。驻留在同一进程中的两个对象可以简单地共享内存中的相同数据。将数据发送到另一台计算机要复杂得多,需要将数据从一台计算机复制到另一台计算机。这意味着对象必须是“可序列化的”,也就是说,它们可以转换为可以通过网络发送的简单字节流。消息传递负责此转换,因此应用程序不必担心它。
Remote Communication. Messaging enables separate applications to communicate and transfer data. Two objects that reside in the same process can simply share the same data in memory. Sending data to another computer is a lot more complicated and requires data to be copied from one computer to another. This means that objects have to be "serializable"that is, they can be converted into a simple byte stream that can be sent across the network. Messaging takes care of this conversion so that the applications do not have to worry about it.
平台/语言集成。当通过远程通信连接多个计算机系统时,这些系统可能使用不同的语言、技术和平台,这可能是因为它们是由独立团队随着时间的推移而开发的。集成此类不同的应用程序可能需要一个中间件的中立区域来在应用程序之间进行协商,通常使用最低的共同点,例如格式模糊的平面数据文件。在这些情况下,消息传递系统可以成为应用程序之间的通用翻译器,它可以按照自己的条件与每个人的语言和平台一起工作,但允许它们通过通用的消息传递范式进行通信。这种通用连接性是消息总线的核心图案。
Platform/Language Integration. When connecting multiple computer systems via remote communication, these systems likely use different languages, technologies, and platforms, perhaps because they were developed over time by independent teams. Integrating such divergent applications can require a neutral zone of middleware to negotiate between the applications, often using the lowest common denominatorsuch as flat data files with obscure formats. In these circumstances, a messaging system can be a universal translator between the applications that works with each one's language and platform on its own terms yet allows them to all to communicate through a common messaging paradigm. This universal connectivity is the heart of the Message Bus pattern.
异步通信。消息传递支持“发送后忘记”的通信方式。发送方不必等待接收方接收并处理消息;它甚至不必等待消息系统传递消息。发送方只需要等待消息发送完毕,即消息被消息系统成功存入通道即可。一旦消息被存储,发送者就可以在后台传输消息的同时自由地执行其他工作。
Asynchronous communication. Messaging enables a send-and-forget approach to communication. The sender does not have to wait for the receiver to receive and process the message; it does not even have to wait for the messaging system to deliver the message. The sender only needs to wait for the message to be sent, that is, for the message to be successfully stored in the channel by the messaging system. Once the message is stored, the sender is free to perform other work while the message is transmitted in the background.
可变时序。对于同步通信,调用者必须等待接收者完成对调用的处理,然后调用者才能收到结果并继续。这样,呼叫者拨打电话的速度只能与接收者执行呼叫的速度一样快。异步通信允许发送者按照自己的节奏向接收者提交请求,并且接收者按照自己的不同节奏消费请求。这允许两个应用程序以最大吞吐量运行,并且不会浪费时间等待彼此(至少直到接收者用完要处理的消息为止)。
Variable timing. With synchronous communication, the caller must wait for the receiver to finish processing the call before the caller can receive the result and continue. In this way, the caller can make calls only as fast as the receiver can perform them. Asynchronous communication allows the sender to submit requests to the receiver at its own pace and the receiver to consume the requests at its own different pace. This allows both applications to run at maximum throughput and not waste time waiting on each other (at least until the receiver runs out of messages to process).
节流。远程过程调用 (RPC) 的一个问题是,单个接收器上同时调用太多远程过程调用可能会使接收器过载。这可能会导致性能下降,甚至导致接收器崩溃。由于消息传递系统会对请求进行排队,直到接收者准备好处理它们,因此接收者可以控制其消耗请求的速率,以免因太多同时请求而导致过载。调用者不受此限制的影响,因为通信是异步的,因此调用者不会因等待接收者而被阻止。
Throttling. A problem with remote procedure calls (RPCs) is that too many of them on a single receiver at the same time can overload the receiver. This can cause performance degradation and even cause the receiver to crash. Because the messaging system queues up requests until the receiver is ready to process them, the receiver can control the rate at which it consumes requests so as not to become overloaded by too many simultaneous requests. The callers are unaffected by this throttling because the communication is asynchronous, so the callers are not blocked waiting on the receiver.
可靠的通讯。消息传递提供了 RPC 无法提供的可靠传递。消息传递比 RPC 更可靠的原因是消息传递使用存储转发方法来传输消息。数据被打包为消息,消息是原子的、独立的单元。当发送者发送消息时,消息系统存储该消息。然后,它通过将消息转发到接收者的计算机来传递消息,并再次存储该消息。假定将消息存储在发送者的计算机和接收者的计算机上是可靠的。(为了使其更加可靠,消息可以存储到磁盘而不是内存;请参阅保证传递.) 不可靠的是将消息从发送者的计算机转发(移动)到接收者的计算机,因为接收者或网络可能无法正常运行。消息传递系统通过重新发送消息直到成功来克服这个问题。这种自动重试使消息传递系统能够克服网络问题,以便发送者和接收者不必担心这些细节。
Reliable communication. Messaging provides reliable delivery that an RPC cannot. The reason messaging is more reliable than RPC is that messaging uses a store-and-forward approach to transmitting messages. The data is packaged as messages, which are atomic, independent units. When the sender sends a message, the messaging system stores the message. It then delivers the message by forwarding it to the receiver's computer, where it is stored again. Storing the message on the sender's computer and the receiver's computer is assumed to be reliable. (To make it even more reliable, the messages can be stored to disk instead of memory; see Guaranteed Delivery.) What is unreliable is forwarding (moving) the message from the sender's computer to the receiver's computer, because the receiver or the network may not be running properly. The messaging system overcomes this by resending the message until it succeeds. This automatic retry enables the messaging system to overcome problems with the network so that the sender and receiver don't have to worry about these details.
断线操作。某些应用程序专门设计为在与网络断开连接的情况下运行,但在网络连接可用时仍与服务器同步。此类应用程序部署在笔记本电脑和 PDA 等平台上。消息传递非常适合使这些应用程序能够同步,要同步的数据可以在创建时排队,等待应用程序重新连接到网络。
Disconnected operation. Some applications are specifically designed to run disconnected from the network, yet to synchronize with servers when a network connection is available. Such applications are deployed on platforms like laptop computers and PDAs. Messaging is ideal for enabling these applications to synchronizedata to be synchronized can be queued as it is created, waiting until the application reconnects to the network.
调解。消息传递系统充当中介者模式[GoF ] 中所有可以发送和接收消息的程序之间的中介者。应用程序可以将其用作可集成的其他应用程序或服务的目录。如果某个应用程序与其他应用程序断开连接,它只需重新连接到消息传递系统,而不需要重新连接到所有其他消息传递应用程序。消息传递系统可以利用冗余资源来提供高可用性、平衡负载、重新路由失败的网络连接以及调整性能和服务质量。
Mediation. The messaging system acts as a mediatoras in the Mediator pattern [GoF]between all of the programs that can send and receive messages. An application can use it as a directory of other applications or services available to integrate with. If an application becomes disconnected from the others, it need only reconnect to the messaging system, not to all of the other messaging applications. The messaging system can employ redundant resources to provide high availability, balance load, reroute around failed network connections, and tune performance and quality of service.
线程管理。异步通信意味着一个应用程序在等待另一应用程序执行任务时不必阻塞,除非它愿意。调用者可以使用回调来在回复到达时提醒调用者,而不是阻塞等待回复。(参见请求-回复模式。)大量被阻止的线程或长时间被阻止的线程可能会使应用程序的可用线程太少而无法执行实际工作。此外,如果具有动态数量的阻塞线程的应用程序崩溃,那么当应用程序重新启动并恢复其先前状态时,重新建立这些线程将很困难。对于回调,唯一阻塞的线程是一小部分已知数量的等待回复的侦听器。这使得大多数线程可用于其他工作,并定义了已知数量的侦听器线程,这些线程可以在崩溃后轻松重新建立。
Thread management. Asynchronous communication means that one application does not have to block while waiting for another application to perform a task, unless it wants to. Rather than blocking to wait for a reply, the caller can use a callback that will alert the caller when the reply arrives. (See the Request-Reply pattern.) A large number of blocked threads or threads blocked for a long time can leave the application with too few available threads to perform real work. Also, if an application with a dynamic number of blocked threads crashes, reestablishing those threads will be difficult when the application restarts and recovers its former state. With callbacks, the only threads that block are a small, known number of listeners waiting for replies. This leaves most threads available for other work and defines a known number of listener threads that can easily be reestablished after a crash.
因此,应用程序或企业可以从消息传递中受益的原因有很多。其中一些是应用程序开发人员最容易涉及的技术细节,而另一些则是最能与企业架构师产生共鸣的战略决策。这些原因中哪一个最重要取决于您特定应用程序的当前要求。它们都是使用消息传递的好理由,因此请充分利用可为您带来最大好处的理由。
So, there are a number of different reasons an application or enterprise may benefit from messaging. Some of these are technical details that application developers relate most readily to, whereas others are strategic decisions that resonate best with enterprise architects. Which of these reasons is most important depends on the current requirements of your particular applications. They're all good reasons to use messaging, so take advantage of whichever reasons provide the most benefit to you.
异步消息传递并不是集成的万能药。它解决了以优雅的方式集成不同系统的许多挑战,但它也带来了新的挑战。其中一些挑战是异步模型固有的,而其他挑战则因消息传递系统的具体实现而异。
Asynchronous messaging is not the panacea of integration. It resolves many of the challenges of integrating disparate systems in an elegant way, but it also introduces new challenges. Some of these challenges are inherent in the asynchronous model, while other challenges vary with the specific implementation of a messaging system.
复杂的编程模型。异步消息传递要求开发人员使用事件驱动的编程模型。应用程序逻辑不再可以在调用其他方法的单个方法中进行编码,而是现在将逻辑分为多个响应传入消息的事件处理程序。这样的系统更加复杂,更难开发和调试。例如,简单方法调用的等价物可能需要请求消息和请求通道、回复消息和回复通道、相关标识符和无效消息队列(如请求-回复中所述)。
Complex programming model. Asynchronous messaging requires developers to work with an event-driven programming model. Application logic can no longer be coded in a single method that invokes other methods, but instead the logic is now split up into a number of event handlers that respond to incoming messages. Such a system is more complex and harder to develop and debug. For example, the equivalent of a simple method call can require a request message and a request channel, a reply message and a reply channel, a correlation identifier and an invalid message queue (as described in Request-Reply).
顺序问题。消息通道保证消息传递,但不保证消息何时传递。这可能会导致按顺序发送的消息乱序。在消息相互依赖的情况下,必须特别注意重新建立消息序列(请参阅Resequencer )。
Sequence issues. Message channels guarantee message delivery, but they do not guarantee when the message will be delivered. This can cause messages that are sent in sequence to get out of sequence. In situations where messages depend on each other, special care has to be taken to reestablish the message sequence (see Resequencer).
同步场景。并非所有应用程序都可以在“发送后不管”模式下运行。如果用户正在寻找机票,他或她会希望立即查看机票价格,而不是在某个不确定的时间之后查看。因此,许多消息系统需要弥合同步和异步解决方案之间的差距。
Synchronous scenarios. Not all applications can operate in a send-and-forget mode. If a user is looking for airline tickets, he or she is going to want to see the ticket price right away, not after some undetermined time. Therefore, many messaging systems need to bridge the gap between synchronous and asynchronous solutions.
性能。消息系统确实增加了一些通信开销。将应用程序数据打包成消息并发送,以及接收消息并处理它都需要花费精力。如果您必须传输大量数据,将其分成无数小块可能不是一个明智的主意。例如,如果集成解决方案需要在两个现有系统之间同步信息,第一步通常是将所有相关信息从一个系统复制到另一个系统。对于这样的批量数据复制步骤,ETL(提取、转换和加载)工具比消息传递更有效。消息传递最适合在初始数据复制后保持系统同步。
Performance. Messaging systems do add some overhead to communication. It takes effort to package application data into a message and send it, and to receive a message and process it. If you have to transport a huge chunk of data, dividing it into a gazillion small pieces may not be a smart idea. For example, if an integration solution needs to synchronize information between two existing systems, the first step is usually to replicate all relevant information from one system to the other. For such a bulk data replication step, ETL (extract, transform, and load) tools are much more efficient than messaging. Messaging is best suited to keeping the systems in sync after the initial data replication.
平台支持有限。许多专有消息传递系统并非在所有平台上都可用。通常,通过 FTP 传输文件是唯一的集成选项,因为目标平台可能不支持消息传递系统。
Limited platform support. Many proprietary messaging systems are not available on all platforms. Often, transferring a file via FTP is the only integration option because the target platform may not support a messaging system.
供应商锁定。许多消息传递系统的实现都依赖于专有协议。即使是常见的消息传递规范(例如 JMS)也无法控制解决方案的物理实现。因此,不同的消息传递系统通常不会相互连接。这可能会给您带来全新的集成挑战:集成多个集成解决方案!(请参阅消息传递桥模式。)
Vendor lock-in. Many messaging system implementations rely on proprietary protocols. Even common messaging specifications such as JMS do not control the physical implementation of the solution. As a result, different messaging systems usually do not connect to one another. This can leave you with a whole new integration challenge: integrating multiple integration solutions! (See the Messaging Bridge pattern.)
总之,异步消息传递并不能解决所有问题,甚至可能会产生新的问题。在决定使用消息传递解决哪些问题时,请记住这些后果。
In summary, asynchronous messaging does not solve all problems, and it can even create new ones. Keep these consequences in mind when deciding which problems to solve using messaging.
消息传递是一种异步技术,可以重试传递直至成功。相反,大多数应用程序使用同步函数调用,例如,一个过程调用子过程、一个方法调用另一个方法,或者一个过程通过 RPC(例如 CORBA 和 DCOM)远程调用另一个过程。同步调用意味着调用进程在子进程执行函数时停止。即使在 RPC 场景中,被调用的子过程在不同的进程中执行,调用者也会阻塞,直到子过程将控制(和结果)返回给调用者。相反,当使用异步消息传递时,调用者使用发送后忽略的方法,允许它在发送消息后继续执行。因此,
Messaging is an asynchronous technology, which enables delivery to be retried until it succeeds. In contrast, most applications use synchronous function callsfor example, a procedure calling a subprocedure, one method calling another method, or one procedure invoking another remotely through an RPC (such as CORBA and DCOM). Synchronous calls imply that the calling process is halted while the subprocess is executing a function. Even in an RPC scenario, where the called subprocedure executes in a different process, the caller blocks until the subprocedure returns control (and the results) to the caller. In contrast, when using asynchronous messaging, the caller uses a send-and-forget approach that allows it to continue to execute after it sends the message. As a result, the calling procedure continues to run while the subprocedure is being invoked (see figure).
同步和异步调用语义
Synchronous and Asynchronous Call Semantics
异步通信有很多含义。首先,我们不再有单个执行线程。多个线程使子过程能够并发运行,这可以极大地提高性能,并有助于确保某些子进程正在取得进展,即使其他子进程可能正在等待外部结果。然而,并发线程也使调试变得更加困难。其次,结果(如果有)通过回调机制到达。这使得调用者能够执行其他任务,并在结果可用时收到通知,从而提高性能。然而,这意味着调用者即使在其他任务中间也必须能够处理结果,并且必须能够记住进行调用的上下文。第三,异步子流程可以按任何顺序执行。同样,这使得一个子过程能够取得进展,即使另一个子过程不能取得进展。但这也意味着子流程必须能够以任何顺序独立运行,并且调用者必须能够确定哪个结果来自哪个子流程并将结果组合在一起。因此,异步通信具有多个优点,但需要重新考虑过程如何使用其子过程。
Asynchronous communication has a number of implications. First, we no longer have a single thread of execution. Multiple threads enable subprocedures to run concurrently, which can greatly improve performance and help ensure that some subprocesses are making progress even while other subprocesses may be waiting for external results. However, concurrent threads also make debugging much more difficult. Second, results (if any) arrive via a callback mechanism. This enables the caller to perform other tasks and be notified when the result is available, which can improve performance. However, this means that the caller has to be able to process the result even while it is in the middle of other tasks, and it has to be able to remember the context in which the call was made. Third, asynchronous subprocesses can execute in any order. Again, this enables one subprocedure to make progress even while another cannot. But it also means that the sub-processes must be able to run independently in any order, and the caller must be able to determine which result came from which subprocess and combine the results together. As a result, asynchronous communication has several advantages but requires rethinking how a procedure uses its subprocedures.
本书讲述的是企业集成如何集成独立的应用程序,以便它们可以协同工作。企业应用程序通常采用n层架构(客户端/服务器架构的更复杂版本),使其能够分布在多台计算机上。尽管这会导致不同机器上的进程相互通信,但这只是应用程序分发,而不是应用程序集成。
This book is about enterprise integrationhow to integrate independent applications so that they can work together. An enterprise application often incorporates an n-tier architecture (a more sophisticated version of a client/server architecture), enabling it to be distributed across several computers. Even though this results in processes on different machines communicating with each other, this is application distribution, not application integration.
为什么n层架构被认为是应用程序分发而不是应用程序集成?首先,通信部分紧密耦合,它们直接相互依赖,因此一层无法在没有其他层的情况下运行。其次,层之间的通信趋于同步。第三,应用程序(n层或原子)往往具有只接受快速系统响应时间的人类用户。
Why is an n-tier architecture considered application distribution and not application integration? First, the communicating parts are tightly coupledthey dependent directly on each other, so one tier cannot function without the others. Second, communication between tiers tends to be synchronous. Third, an application (n-tier or atomic) tends to have human users who will only accept rapid system response times.
相比之下,集成应用程序是独立的应用程序,每个应用程序都可以自行运行,但以松散耦合的方式相互协调。这使得每个应用程序能够专注于一组全面的功能,同时将相关功能委托给其他应用程序。异步通信的集成应用程序不必等待响应;他们可以在没有响应的情况下继续进行,也可以同时执行其他任务,直到有响应为止。集成应用程序往往具有广泛的时间限制,因此它们可以处理其他任务,直到获得结果,因此比大多数实时等待结果的人类用户更有耐心。
In contrast, integrated applications are independent applications that can each run by themselves but that coordinate with each other in a loosely coupled way. This enables each application to focus on one comprehensive set of functionality and yet delegate to other applications for related functionality. Integrated applications communicating asynchronously don't have to wait for a response; they can proceed without a response or perform other tasks concurrently until the response is available. Integrated applications tend to have a broad time constraint, such that they can work on other tasks until a result becomes available, and therefore are more patient than most human users waiting real-time for a result.
使用异步消息传递解决方案集成系统的明显好处为创建消息传递中间件和相关工具的软件供应商开辟了一个重要的市场。我们可以将消息厂商的产品大致分为以下四类:
The apparent benefits of integrating systems using an asynchronous messaging solution have opened up a significant market for software vendors creating messaging middleware and associated tools. We can roughly group the messaging vendors' products into the following four categories:
操作系统。消息传递已经成为一种普遍的需求,以至于供应商已经开始将必要的软件基础设施集成到操作系统或数据库平台中。例如,Microsoft Windows 2000 和Windows XP 操作系统包括Microsoft 消息队列(MSMQ) 服务软件。该服务可通过许多 API 访问,包括 COM 组件和 System.Messaging 命名空间(Microsoft .NET 平台的一部分)。同样,Oracle 提供 Oracle AQ 作为其数据库平台的一部分。
Operating systems. Messaging has become such a common need that vendors have started to integrate the necessary software infrastructure into the operating system or database platform. For example, the Microsoft Windows 2000 and Windows XP operating systems include the Microsoft Message Queuing (MSMQ) service software. This service is accessible through a number of APIs, including COM components and the System.Messaging namespace, part of the Microsoft .NET platform. Similarly, Oracle offers Oracle AQ as part of its database platform.
应用服务器。Sun Microsystems 首先将 Java 消息服务 (JMS) 合并到 J2EE 规范 1.2 版中。从那时起,几乎所有 J2EE 应用服务器(例如 IBM WebSphere 和 BEA WebLogic)都提供了该规范的实现。此外,Sun 还通过 J2EE JDK 提供了 JMS 参考实现。
Application servers. Sun Microsystems first incorporated the Java Messaging Service (JMS) into version 1.2 of the J2EE specification. Since then, virtually all J2EE application servers (such as IBM WebSphere and BEA WebLogic) provide an implementation for this specification. Also, Sun delivers a JMS reference implementation with the J2EE JDK.
EAI 套件。这些供应商的产品提供专有但功能丰富的套件,包括消息传递、业务流程自动化、工作流程、门户和其他功能。该市场的主要参与者包括 IBM WebSphere MQ、Microsoft BizTalk、TIBCO、WebMethods、SeeBeyond、Vitria、CrossWorlds 等。其中许多产品都将 JMS 作为其支持的众多客户端 API 之一,而 SonicSoftware 和 Fiorano 等其他供应商主要专注于实现符合 JMS 的消息传递基础设施。
EAI suites. Products from these vendors offer proprietarybut functionally richsuites that encompass messaging, business process automation, workflow, portals, and other functions. Key players in this marketplace are IBM WebSphere MQ, Microsoft BizTalk, TIBCO, WebMethods, SeeBeyond, Vitria, CrossWorlds, and others. Many of these products include JMS as one of the many client APIs they support, while other vendorssuch as SonicSoftware and Fioranofocus primarily on implementing JMS-compliant messaging infrastructures.
Web 服务工具包。Web 服务引起了企业集成社区的极大兴趣。标准机构和联盟正在积极致力于标准化 Web 服务上的可靠消息传递(即 WS-Reliability、WS-ReliableMessaging 和 ebMS)。越来越多的供应商提供了实现基于 Web 服务的解决方案的路由、转换和管理的工具。
Web services toolkits. Web services have garnered a lot of interest in the enterprise integration communities. Standards bodies and consortia are actively working on standardizing reliable message delivery over Web services (i.e., WS-Reliability, WS-ReliableMessaging, and ebMS). A growing number of vendors offer tools that implement routing, transformation, and management of Web services-based solutions.
本书中的模式与供应商无关,适用于大多数消息传递解决方案。不幸的是,每个供应商在描述消息传递解决方案时都倾向于定义自己的术语。在本书中,我们努力选择技术和产品中立但具有描述性且易于对话使用的模式名称。
The patterns in this book are vendor-independent and apply to most messaging solutions. Unfortunately, each vendor tends to define its own terminology when describing messaging solutions. In this book, we strove to choose pattern names that are technology- and product-neutral yet descriptive and easy to use conversationally.
许多消息传递供应商已将本书的一些模式作为其产品的功能,这简化了模式的应用并加速了解决方案的开发。熟悉特定供应商术语的读者很可能会认识本书中的许多概念。为了帮助这些读者将模式语言映射到供应商特定的术语,下表将最常见的模式名称映射到一些最广泛使用的消息传递产品中相应的产品功能名称。
Many messaging vendors have incorporated some of this book's patterns as features of their products, which simplifies applying the patterns and accelerates solution development. Readers who are familiar with a particular vendor's terminology will most likely recognize many of the concepts in this book. To help these readers map the pattern language to the vendor-specific terminology, the following tables map the most common pattern names to their corresponding product feature names in some of the most widely used messaging products.
企业集成模式 Enterprise Integration Patterns | Java消息服务(JMS ) Java Message Service (JMS) | 微软MSMQ Microsoft MSMQ | WebSphere MQ WebSphere MQ |
|---|---|---|---|
留言通道 Message Channel | 目的地 Destination | 消息队列 MessageQueue | 队列 Queue |
点对点通道 Point-to-Point Channel | 队列 Queue | 消息队列 MessageQueue | 队列 Queue |
发布订阅通道 Publish-Subscribe Channel | 话题 Topic | ||
信息 Message | 信息 Message | 信息 Message | 信息 Message |
消息端点 Message Endpoint | 消息生产者、消息消费者 MessageProducer, MessageConsumer |
企业集成模式 Enterprise Integration Patterns | TIBCO TIBCO | 网络方法 WebMethods | 超越 SeeBeyond | 维特里亚 Vitria |
|---|---|---|---|---|
留言通道 Message Channel | 主题 Subject | 队列 Queue | 智能排队 Intelligent Queue | 渠道 Channel |
点对点通道 Point-to-Point Channel | 分布式队列 Distributed Queue | 采取行动 Deliver Action | 智能排队 Intelligent Queue | 渠道 Channel |
发布订阅通道 Publish-Subscribe Channel | 主题 Subject | 发布-订阅操作 Publish-Subscribe Action | 智能排队 Intelligent Queue | 发布订阅通道 Publish-Subscribe Channel |
信息 Message | 信息 Message | 文档 Document | 事件 Event | 事件 Event |
消息端点 Message Endpoint | 发布者、订阅者 Publisher, Subscriber | 发布者、订阅者 Publisher, Subscriber | 发布者、订阅者 Publisher, Subscriber | 发布者、订阅者 Publisher, Subscriber |
本书包含一组组织成模式语言的模式。《设计模式》、《面向模式J2EE的模式》和《企业应用程序架构模式》等书籍已经普及了使用模式来记录计算机编程技术的概念。Christopher Alexander 在他的著作《A Pattern Language》和《A Timeless Way of Building》中开创了模式和模式语言的概念。每个模式都代表一个必须做出的决定以及该决定中的考虑因素。模式语言是一个由相关模式组成的网络,其中每个模式都引向其他模式,从而指导您完成决策过程。这种方法是一种强大的技术,可以记录专家的知识,以便其他人可以轻松理解和应用。
This book contains a set of patterns organized into a pattern language. Books such as Design Patterns, Pattern Oriented Software Architecture, Core J2EE Patterns, and Patterns of Enterprise Application Architecture have popularized the concept of using patterns to document computer-programming techniques. Christopher Alexander pioneered the concept of patterns and pattern languages in his books A Pattern Language and A Timeless Way of Building. Each pattern represents a decision that must be made and the considerations that go into that decision. A pattern language is a web of related patterns where each pattern leads to others, guiding you through the decision-making process. This approach is a powerful technique for documenting an expert's knowledge so that it can be readily understood and applied by others.
模式语言教您如何在有限的问题空间内解决无限种问题。由于每次要解决的总体问题都不同,因此模式的路径及其应用方式也是独特的。本书是为任何在任何应用程序中使用任何消息传递工具的人编写的,它可以专门针对您以及您所面临的独特的消息传递应用程序。
A pattern language teaches you how to solve a limitless variety of problems within a bounded problem space. Because the overall problem that is being solved is different every time, the path through the patterns and how they're applied is also unique. This book is written for anyone using any messaging tools for any application, and it can be applied specifically for you and the unique application of messaging that you face.
单独使用模式形式并不能保证一本书包含丰富的知识。仅仅说“当你遇到这个问题时,应用这个解决方案”是不够的。为了让您真正从模式中学习,该模式必须记录为什么问题难以解决,考虑实际上效果不佳的可能解决方案,并解释为什么所提供的解决方案是最好的解决方案。同样,这些模式需要相互连接,以便引导您从一个问题到下一个问题。通过这种方式,模式形式不仅可以用来教导应用什么解决方案,还可以用来教导如何解决作者无法预测的问题。这些是我们在本书中努力实现的目标。
Using the pattern form by itself does not guarantee that a book contains a wealth of knowledge. It is not enough to simply say, "When you face this problem, apply this solution." For you to truly learn from a pattern, the pattern has to document why the problem is difficult to solve, consider possible solutions that in fact don't work well, and explain why the solution offered is the best available. Likewise, the patterns need to connect to each other so as to walk you from one problem to the next. In this way, the pattern form can be used to teach not just what solutions to apply but also how to solve problems the authors could not have predicted. These are goals we strive to accomplish in this book.
模式应该是规范性的,这意味着它们应该告诉您要做什么。它们不只是描述问题,也不只是描述如何解决问题,而是告诉您如何解决问题。每个模式代表您必须做出的决定:“我应该使用消息传递吗? ” “命令信息对我有帮助吗?” 模式和模式语言的目的是帮助您做出决策,从而为您的特定问题提供良好的解决方案,即使作者没有考虑到该特定问题,即使您没有知识和经验自己开发该解决方案的经验。
Patterns should be prescriptive, meaning that they should tell you what to do. They don't just describe a problem, and they don't just describe how to solve itthey tell you what to do to solve it. Each pattern represents a decision you must make: "Should I use Messaging?" "Would a Command Message help me here?" The point of the patterns and the pattern language is to help you make decisions that lead to a good solution for your specific problem, even if the authors didn't have that specific problem in mind and even if you don't have the knowledge and experience to develop that solution on your own.
没有一种通用的模式形式;不同的书使用不同的结构。我们使用了一种与亚历山大形式相当接近的风格,这种风格首先在Kent Beck的《Smalltalk 最佳实践模式》中在计算机编程中得到普及。我们喜欢亚历山大形式,因为它产生的模式更像散文。因此,尽管每个模式都遵循相同的、定义明确的结构,但该格式避免了各个小节的标题,这会破坏讨论的流程。为了提高导航性,该格式使用下划线、缩进和插图等样式元素来帮助您快速识别重要信息。
There is no one universal pattern form; different books use various structures. We used a style that is fairly close to the Alexandrian form, which was first popularized for computer programming in Smalltalk Best Practice Patterns by Kent Beck. We like the Alexandrian form because it results in patterns that are more prose-like. As a result, even though each pattern follows an identical, well-defined structure, the format avoids headings for individual subsections, which would disrupt the flow of the discussion. To improve navigability, the format uses style elements such as underscoring, indentation, and illustrations to help you identify important information at a quick glance.
每个模式都遵循以下结构:
Each pattern follows this structure:
名称 这是模式的标识符,指示模式的用途。我们选择了易于在句子中使用的名称,以便在设计师之间的对话中轻松引用模式的概念。
Name This is an identifier for the pattern that indicates what the pattern does. We chose names that can easily be used in a sentence so that it is easy to reference the pattern's concept in a conversation between designers.
图标 除了模式名称之外,大多数模式还与图标相关联。由于许多建筑师习惯于通过图表进行视觉交流,因此除了口头语言之外,我们还提供视觉语言。这种视觉语言强调了模式的可组合性,因为可以组合多个模式图标来描述更大、更复杂模式的解决方案。
Icon Most patterns are associated with an icon in addition to the pattern name. Because many architects are used to communicating visually through diagrams, we provide a visual language in addition to the verbal language. This visual language underlines the composability of the patterns, as multiple pattern icons can be combined to describe the solution of a larger, more complex pattern.
上下文 本节解释什么类型的工作可能会让您遇到此模式解决的问题。上下文为问题奠定了基础,并且通常指的是您可能已经应用的其他模式。
Context This section explains what type of work might make you run into the problem that this pattern solves. The context sets the stage for the problem and often refers to other patterns you may have already applied.
问题 这解释了您所面临的困难,以问题的形式表达。您应该能够阅读问题陈述并快速确定该模式是否与您的工作相关。我们已将问题格式化为由水平规则分隔的一个句子。
Problem This explains the difficulty you are facing, expressed as a question. You should be able to read the problem statement and quickly determine if this pattern is relevant to your work. We've formatted the problem to be one sentence delimited by horizontal rules.
力量 力量探索使问题难以解决的限制因素。他们经常考虑看似有前途但效果不佳的替代解决方案,这有助于展示真正解决方案的价值。
Forces The forces explore the constraints that make the problem difficult to solve. They often consider alternative solutions that seem promising but don't pan out, which helps show the value of the real solution.
解决方案 本部分说明您应该采取哪些措施来解决该问题。它不限于您的特定情况,而是描述了在问题所代表的各种情况下该怎么做。如果您了解模式的问题和解决方案,您就了解了该模式。我们采用与问题相同的风格来格式化解决方案,以便您在阅读本书时可以轻松发现问题和解决方案的陈述。
Solution This part explains what you should do to solve the problem. It is not limited to your particular situation, but describes what to do in the variety of circumstances represented by the problem. If you understand a pattern's problem and solution, you understand the pattern. We've formatted the solution in the same style as the problem so that you can easily spot problem and solution statements when perusing the book.
草图 亚历山大形式最吸引人的特性之一是每个模式都包含一个说明解决方案的草图。很多时候,只要看图案名称和草图,就可以了解图案的本质。我们试图通过紧跟每个模式的解决方案陈述之后用图来说明解决方案来保持这种风格。
Sketch One of the most appealing properties of the Alexandrian form is that each pattern contains a sketch that illustrates the solution. In many cases, just by looking at the pattern name and the sketch, you can understand the essence of the pattern. We tried to maintain this style by illustrating the solution with a figure immediately following the solution statement of each pattern.
结果 这部分对解决方案进行了扩展,以解释如何应用该解决方案以及如何解决力的详细信息。它还解决了应用此模式可能出现的新挑战。
Results This part expands upon the solution to explain the details of how to apply the solution and how it resolves the forces. It also addresses new challenges that may arise as a result of applying this pattern.
下一步 本节列出了应用当前模式后要考虑的其他模式。模式并不是孤立存在的;一种模式的应用通常会导致您遇到其他模式可以解决的新问题。模式之间的关系构成了模式语言,而不仅仅是模式目录。
Next This section lists other patterns to be considered after applying the current one. Patterns don't live in isolation; the application of one pattern usually leads you to new problems that are solved by other patterns. The relationships between patterns are what constitutes a pattern language as opposed to just a pattern catalog.
侧边栏 这些部分讨论更详细的技术问题或模式的变体。我们将这些部分在视觉上与文本的其余部分分开,因此如果它们与您的模式的特定应用不相关,您可以轻松地跳过它们。
Sidebars These sections discuss more detailed technical issues or variations of the pattern. We set these sections visually apart from the remainder of the text so you can easily skip them if they are not relevant to your particular application of the pattern.
示例 图案通常包括正在应用或已经应用的图案的一个或多个示例。示例可以像命名已知用途一样简单,也可以像一大段示例代码一样详细。鉴于可用的消息传递技术有大量,我们不希望您熟悉用于实现示例的每种技术。因此,我们设计了这些模式,以便您可以安全地跳过该示例,而不会丢失该模式的任何关键内容。
Examples A pattern usually includes one or more examples of the pattern being applied or having been applied. An example may be as simple as naming a known use or as detailed as a large segment of sample code. Given the large number of available messaging technologies, we do not expect you to be familiar with each technology used to implement an example. Therefore, we designed the patterns so that you can safely skip the example without losing any critical content of the pattern.
将解决方案描述为模式的美妙之处在于,它不仅教您如何解决所讨论的具体问题,还教您如何创建解决作者甚至没有意识到的问题的设计。因此,这些消息传递模式不仅描述了当今存在的消息传递系统,而且还可能适用于本书出版后创建的新系统。
The beauty in describing solutions as patterns is that it teaches you not only how to solve the specific problems discussed, but also how to create designs that solve problems the authors were not even aware of. As a result, these patterns for messaging not only describe messaging systems that exist today, but may also apply to new ones created well after this book is published.
集成解决方案由许多不同的部分组成:应用程序、数据库、端点、通道、消息、路由器等。如果我们想要描述一个集成解决方案,我们需要定义一个能够容纳所有这些不同组件的符号。据我们所知,还没有一种广泛使用的、全面的符号可以用来描述集成解决方案的各个方面。统一建模语言 (UML) 在使用类图和交互图描述面向对象的系统方面做得很好,但它不包含描述消息传递解决方案的语义。EAI 的 UML 概要文件 [ UMLEAI] 丰富了协作图的语义来描述组件之间的消息流。这种表示法作为精确的可视化规范非常有用,可以作为模型驱动架构 (MDA) 一部分的代码生成的基础。我们决定不采用这种表示法有两个原因。首先,UML Profile 并没有捕获我们的模式语言中描述的所有模式。其次,我们并不是要创建精确的视觉规范,而是要创建具有一定“草图”质量的图像。我们想要的图片能够一目了然地传达图案的本质,就像亚历山大的草图一样。这就是为什么我们决定创建我们自己的“符号”。幸运的是,与更正式的符号不同,我们的符号不需要您阅读大量手册。一张简单的图片就足够了:
Integration solutions consist of many different piecesapplications, databases, endpoints, channels, messages, routers, and so on. If we want to describe an integration solution, we need to define a notation that accommodates all these different components. To our knowledge, there is no widely used, comprehensive notation that is geared toward the description of all aspects of an integration solution. The Unified Modeling Language (UML) does a fine job of describing object-oriented systems with class and interaction diagrams, but it does not contain semantics to describe messaging solutions. The UML Profile for EAI [UMLEAI] enriches the semantics of collaboration diagrams to describe message flows between components. This notation is very useful as a precise visual specification that can serve as the basis for code generation as part of a model-driven architecture (MDA). We decided not to adopt this notation for two reasons. First, the UML Profile does not capture all the patterns described in our pattern language. Second, we were not looking to create a precise visual specification, but images that have a certain "sketch" quality to them. We wanted pictures that are able to convey the essence of a pattern at a quick glancevery much like Alexander's sketch. That's why we decided to create our own "notation." Luckily, unlike the more formal notation, ours does not require you to read a large manual. A simple picture should suffice:
消息传递解决方案的视觉符号
Visual Notation for Messaging Solutions
这个简单的图片显示了通过通道发送到组件的消息。我们使用“组件”这个词这里它可以非常宽松地指示正在集成的应用程序、在应用程序之间转换或路由消息的中介或应用程序的特定部分。有时,如果我们想突出通道本身,我们也会将通道描绘为三维管道。通常,我们对组件更感兴趣,并将通道绘制为带箭头的简单线条。这两种表示法是等效的。我们将消息描述为一棵小树,具有圆形根和嵌套的方形元素,因为许多消息传递系统允许消息包含树状数据结构(例如 XML 文档)。树元素可以加阴影或着色以突出它们在特定模式中的用途。
This simple picture shows a message being sent to a component over a channel. We use the word component very loosely hereit can indicate an application that is being integrated, an intermediary that transforms or routes the message between applications, or a specific part of an application. Sometimes, we also depict a channel as a three-dimensional pipe if we want to highlight the channel itself. Often, we are more interested in the components and draw the channels as simple lines with arrow heads. The two notations are equivalent. We depict the message as a small tree with a round root and nested, square elements because many messaging systems allow messages to contain tree-like data structuresfor example, XML documents. The tree elements can be shaded or colored to highlight their usage in a particular pattern. Depicting messages in this way allows us to provide a quick visual description of transformation patternsit is easy to show a pattern that adds, rearranges, or removes fields from the message.
当我们描述应用程序设计时,例如,消息传递端点或用 C# 或 Java 编写的示例,我们确实使用标准 UML 类和序列图来描述类层次结构和对象之间的交互,因为 UML 表示法被广泛接受为描述这些类型的标准方式。解决方案(如果您需要复习 UML,请查看 [ UML ])。
When we describe application designsfor example, messaging endpoints or examples written in C# or Javawe do use standard UML class and sequence diagrams to depict the class hierarchy and the interaction between objects because the UML notation is widely accepted as the standard way of describing these types of solutions (if you need a refresher on UML, have a look at [UML]).
我们试图通过包含使用各种集成技术的实现示例来强调这些模式的广泛适用性。这种方法的潜在缺点是您可能不熟悉示例中使用的每种技术。这就是为什么我们确保阅读示例是严格可选的,所有相关点都在模式描述中讨论。因此,您可以安全地跳过这些示例,而不会丢失重要细节。此外,在可能的情况下,我们提供了多个使用不同技术的实现示例。
We have tried to underline the broad applicability of the patterns by including implementation examples using a variety of integration technologies. The potential downside of this approach is that you may not be familiar with each technology that is being used in an example. That's why we made sure that reading the examples is strictly optionalall relevant points are discussed in the pattern description. Therefore, you can safely skip the examples without risk of losing out on important detail. Also, where possible, we provided more than one implementation example using different technologies.
在提供示例代码时,我们关注的是可读性而不是可运行性。代码段可以帮助消除解决方案描述留下的任何潜在歧义,许多应用程序开发人员和架构师更喜欢查看 30 行代码而不是阅读许多文本段落。为了支持这一意图,我们通常只显示潜在更大解决方案中最相关的方法或类。我们还省略了大多数形式的错误检查,以突出代码实现的核心功能。大多数代码片段不包含内嵌注释,因为代码在代码段之前和之后的段落中进行了解释。
When presenting example code, we focused on readability over runnability. A code segment can help remove any potential ambiguity left by the solution description, and many application developers and architects prefer looking at 30 lines of code to reading many paragraphs of text. To support this intent, we often show only the most relevant methods or classes of a potentially larger solution. We also omitted most forms of error checking to highlight the core function implemented by the code. Most code snippets do not contain in-line comments, as the code is explained in the paragraphs before and after the code segment.
为单一集成模式提供有意义的示例具有挑战性。企业集成解决方案通常由分布在多个系统中的许多异构组件组成。同样,大多数集成模式并不是孤立运行的,而是依赖其他模式来形成有意义的解决方案。为了强调多种模式之间的协作,我们提供了更全面的示例作为插曲(请参阅第 6 章、第 9 章和第12 章)。这些解决方案说明了设计更全面的消息传递解决方案时涉及的许多权衡。
Providing a meaningful example for a single integration pattern is challenging. Enterprise integration solutions typically consist of a number of heterogeneous components spread across multiple systems. Likewise, most integration patterns do not operate in isolation but rely on other patterns to form a meaningful solution. To highlight the collaboration between multiple patterns, we included more comprehensive examples as interludes (see Chapters 6, 9, and 12). These solutions illustrate many of the trade-offs involved in designing a more comprehensive messaging solution.
所有代码示例仅应视为说明性工具,而不应作为开发生产质量集成解决方案的起点。例如,几乎所有示例都缺乏任何形式的错误检查或对鲁棒性、安全性或可扩展性的关注。
All code samples should be treated as illustrative tools only and not as a starting point for development of a production-quality integration solution. For example, almost all examples lack any form of error checking or concern for robustness, security, or scalability.
我们尽可能地将示例基于免费或试用版的软件平台。在某些情况下,我们使用商业平台(例如 TIBCO ActiveEnterprise 和 Microsoft BizTalk)来说明从头开始开发解决方案与使用商业工具之间的区别。我们以这样的方式呈现这些示例,即使您无法访问所需的运行时平台,它们也具有教育意义。对于许多示例,我们使用相对简单的消息传递框架,例如 JMS 或 MSMQ。这使我们能够在示例中更加明确,并专注于手头的问题,而不是使用更复杂的中间件工具集可能提供的所有功能来分散注意力。
We tried as much as possible to base the examples on software platforms that are available free of charge or as a trial version. In some cases, we used commercial platforms (such as TIBCO ActiveEnterprise and Microsoft BizTalk) to illustrate the difference between developing a solution from scratch and using a commercial tool. We presented those examples in such a way that they are educational even if you do not have access to the required runtime platform. For many examples, we use relatively barebones messaging frameworks such as JMS or MSMQ. This allows us to be more explicit in the example and focus on the problem at hand instead of distracting from it with all the features a more complex middleware toolset may provide.
本书中的Java 示例基于JMS 1.1 规范,该规范是J2EE 1.4 规范的一部分。到本书出版时,大多数消息传递和应用程序服务器供应商都将支持 JMS 1.1。您可以从 Sun 的网站下载 Sun Microsystems 的 JMS 规范参考实现: http: //java.sun.com/j2ee。
The Java examples in this book are based on the JMS 1.1 specification, which is part of the J2EE 1.4 specification. By the time this book is published, most messaging and application server vendors will support JMS 1.1. You can download Sun Microsystems' reference implementation of the JMS specification from Sun's Web site: http://java.sun.com/j2ee.
Microsoft .NET 示例基于 .NET Framework 1.1 版,并用 C# 编写。您可以从 Microsoft 网站下载 .NET Framework SDK:http://msdn.microsoft.com/net。
The Microsoft .NET examples are based on Version 1.1 of the .NET Framework and are written in C#. You can download the .NET Framework SDK from Microsoft's Web site: http://msdn.microsoft.com/net.
本书中的模式语言与任何模式语言一样,是一个相互引用的模式网络。同时,某些模式比其他模式更基本,形成大概念模式的层次结构,从而导致更精细的模式。大概念模式构成了模式语言的承载成员。它们是主要的模式,是提供语言基础并支持其他模式的根模式。
The pattern language in this book, as with any pattern language, is a web of patterns referring to each other. At the same time, some patterns are more fundamental than others, forming a hierarchy of big-concept patterns that lead to more finely detailed patterns. The big-concept patterns form the load-bearing members of the pattern language. They are the main ones, the root patterns that provide the foundation of the language and support the other patterns.
本书按抽象级别和主题领域将模式分为几章。下图显示了根模式及其与本书各章节的关系。
This book groups patterns into chapters by level of abstraction and by topic area. The following diagram shows the root patterns and their relationship to the chapters of the book.
根模式和章节的关系
Relationship of Root Patterns and Chapters
最基本的模式是消息传递;这就是本书的主题。它导致了第 3 章“消息系统”中描述的六种根模式,即消息通道、消息、管道和过滤器、消息路由器、消息转换器和消息端点。反过来,每个根模式都会导致本书中自己的章节(管道和过滤器除外,它们不是特定于消息传递的,而是一种广泛使用的架构风格,构成了路由和转换模式的基础)。
The most fundamental pattern is Messaging; that's what this book is about. It leads to the six root patterns described in Chapter 3, "Messaging Systems," namely, Message Channel, Message, Pipes and Filters, Message Router, Message Translator, and Message Endpoint. In turn, each root pattern leads to its own chapter in the book (except Pipes and Filters, which is not specific to messaging but is a widely used architectural style that forms the basis of the routing and transformation patterns).
模式语言分为八章,遵循刚刚描述的层次结构:
The pattern language is divided into eight chapters, which follow the hierarchy just described:
第 2 章,“集成样式” 本章回顾了可用于集成应用程序的不同方法,包括消息传递。
第 3 章,“消息传递系统” 本章回顾了六种根消息传递模式,概述了整个模式语言。
第 4 章,“消息传递通道” 应用程序通过通道进行通信。通道定义消息可以遵循的逻辑路径。本章介绍如何确定您的应用程序需要哪些通道。
第 5 章,“消息构造” 一旦有了消息通道,您就需要在它们上发送消息。本章解释了使用消息的不同方式以及如何利用它们的特殊属性。
第 7 章,“消息路由” 消息传递解决方案旨在解耦信息的发送者和接收者。消息路由器提供发送者和接收者之间的位置独立性,以便发送者不必知道谁处理他们的消息。相反,它们将消息发送到中间消息路由组件,中间消息路由组件将消息转发到正确的目的地。本章介绍了各种不同的路由技术。
第 8 章,“消息转换” 独立开发的应用程序通常在消息格式、所谓唯一标识符的形式和含义、甚至要使用的字符编码方面不一致。因此,需要中间组件将消息从一个应用程序生成的格式转换为接收应用程序的格式。本章介绍如何设计变压器组件。
第 10 章,“消息传送端点” 许多应用程序并不是为参与消息传送解决方案而设计的。因此,它们必须显式连接到消息传递系统。本章描述应用程序中负责发送和接收消息的层,使您的应用程序成为消息的端点。
第 11 章,“系统管理” 一旦消息系统准备好集成应用程序,我们如何确保它正确运行并执行我们想要的操作?本章探讨如何测试和监视正在运行的消息系统。
Chapter 2, "Integration Styles" This chapter reviews the different approaches available for integrating applications, including Messaging.
Chapter 3, "Messaging Systems" This chapter reviews the six root messaging patterns, giving an overview of the entire pattern language.
Chapter 4, "Messaging Channels" Applications communicate via channels. Channels define the logical pathways a message can follow. This chapter shows how to determine what channels your applications need.
Chapter 5, "Message Construction" Once you have message channels, you need messages to send on them. This chapter explains the different ways messages can be used and how to take advantage of their special properties.
Chapter 7, "Message Routing" Messaging solutions aim to decouple the sender and the receiver of information. Message routers provide location independence between sender and receiver so that senders don't have to know about who processes their messages. Rather, they send the messages to intermediate message routing components that forward the message to the correct destination. This chapter presents a variety of different routing techniques.
Chapter 8, "Message Transformation" Independently developed applications often don't agree on messages' formats, on the form and meaning of supposedly unique identifiers, or even on the character encoding to be used. Therefore, intermediate components are needed to convert messages from the format one application produces to that of the receiving applications. This chapter shows how to design transformer components.
Chapter 10, "Messaging Endpoints" Many applications were not designed to participate in a messaging solution. As a result, they must be explicitly connected to the messaging system. This chapter describes a layer in the application that is responsible for sending and receiving the messages, making your application an endpoint for messages.
Chapter 11, "System Management" Once a messaging system is in place to integrate applications, how do we make sure that it's running correctly and doing what we want? This chapter explores how to test and monitor a running messaging system.
这八章共同教您有关使用消息传递连接应用程序所需了解的知识。
These eight chapters together teach you what you need to know about connecting applications using messaging.
对于任何一本有很多东西要教的书,对于作者和读者来说都很难知道从哪里开始。直接阅读所有页面可以确保涵盖整个主题领域,但这并不是解决最有帮助的问题的最快方法。从语言中间的模式开始就像开始看一部看了一半的电影一样,你看到正在发生的事情,但不明白它的含义。
With any book that has a lot to teach, it's hard to know where to start, both for the authors and the readers. Reading all of the pages straight through assures covering the entire subject area but isn't the quickest way to get to the issues that are of the most help. Starting with a pattern in the middle of the language can be like starting to watch a movie that's half overyou see what's happening but don't understand what it means.
幸运的是,模式语言是围绕前面描述的根模式形成的。这些根模式共同提供了模式语言的概述,并单独提供了深入研究消息传递细节的起点。要在不回顾所有模式的情况下对语言进行全面调查,请从回顾第 3 章中的根模式开始。
Luckily, the pattern language is formed around the root patterns described earlier. These root patterns collectively provide an overview of the pattern language, and individually provide starting points for delving deep into the details of messaging. To get an overall survey of the language without reviewing all of the patterns, start with reviewing the root patterns in Chapter 3.
第 2 章“集成风格”概述了四种主要的应用程序集成技术,并确定消息传递是许多集成机会的最佳整体方法。如果您不熟悉应用程序集成中涉及的问题以及各种可用方法的优缺点,请阅读本章。如果您已经确信消息传递是正确的选择,并且想要开始了解如何使用消息传递,则可以完全跳过本章。
Chapter 2, "Integration Styles," provides an overview of the four main application integration techniques and settles on Messaging as being the best overall approach for many integration opportunities. Read this chapter if you are unfamiliar with issues involved in application integration and the pros and cons of the various approaches that are available. If you're already convinced that messaging is the way to go and want to get started with how to use messaging, you can skip this chapter completely.
第 3 章“消息传递系统”包含该模式语言的所有根模式(除了第2 章中的消息传递) 。要了解模式语言的概述,请阅读(或至少浏览)本章中的所有模式。要深入研究特定主题,请阅读其根模式,然后转到模式部分末尾提到的模式;接下来的模式都将位于以根模式命名的章节中。
Chapter 3, "Messaging Systems," contains all of this pattern language's root patterns (except Messaging, which is in Chapter 2). For an overview of the pattern language, read (or at least skim) all of the patterns in this chapter. To dive deeply on a particular topic, read its root pattern, then go to the patterns mentioned at the end of the pattern section; those next patterns will all be in a chapter named after the root pattern.
在第 2 章和第 3 章之后,不同类型的消息传递开发人员可能对不同的章节最感兴趣,具体取决于每个组如何使用消息传递执行集成的具体情况:
After Chapters 2 and 3, different types of messaging developers may be most interested in different chapters based on the specifics of how each group uses messaging to perform integration:
系统管理员可能最感兴趣的是第4 章“消息传递通道”(有关创建哪些通道的指南)和第11 章“系统管理”(有关如何维护正在运行的消息传递系统的指南)。
System administrators may be most interested in Chapter 4, "Messaging Channels," the guidelines for what channels to create, and Chapter 11, "System Management," guidance on how to maintain a running messaging system.
应用程序开发人员应该查看第 10 章“消息传送端点”,了解如何将应用程序与消息传送系统集成,并查看第5 章“消息构造”,了解何时发送哪些消息。
Application developers should look at Chapter 10, "Messaging Endpoints," to learn how to integrate an application with a messaging system and at Chapter 5, "Message Construction," to learn what messages to send when.
系统集成商将从第 7 章“消息路由”如何将消息定向到正确的接收者和第 8 章“消息转换”如何将消息从发送者的格式转换为接收者的格式中获益匪浅。
System integrators will gain the most from Chapter 7, "Message Routing"how to direct messages to the proper receiversand Chapter 8, "Message Transformation"how to convert messages from the sender's format to the receiver's.
请记住,在阅读模式时,如果您很着急,请先阅读问题和解决方案。这将为您提供足够的信息来确定您现在是否对该模式感兴趣以及您是否已经了解该模式。如果您不知道该模式并且听起来很有趣,请继续阅读其他部分。
Keep in mind that when reading a pattern, if you're in a hurry, start by just reading the problem and solution. This will give you enough information to determine if the pattern is of interest to you right now and if you already know the pattern. If you do not know the pattern and it sounds interesting, go ahead and read the other parts.
还要记住,这是一种模式语言,因此模式不一定要按照书中呈现的顺序来阅读。本书的顺序通过依次考虑所有相关主题并一起讨论相关问题来教您有关消息传递的知识。要使用模式来解决特定问题,请从适当的根模式开始。它的上下文解释了在这一模式之前需要应用哪些模式,即使它们不是本书中紧邻这一模式之前的模式。同样,下一节(模式的最后一段)描述了在本模式之后考虑应用哪些模式,即使它们不是书中紧随本模式之后的模式。使用相互关联的模式网络,而不是线性的书页列表,
Also remember that this is a pattern language, so the patterns are not necessarily meant to be read in the order they're presented in the book. The book's order teaches you about messaging by considering all of the relevant topics in turn and discussing related issues together. To use the patterns to solve a particular problem, start with an appropriate root pattern. Its context explains what patterns need to be applied before this one, even if they're not the ones immediately preceding this one in the book. Likewise, the next section (the last paragraph of the pattern) describes what patterns to consider applying after this one, even if they're not the ones immediately following this one in the book. Use the web of interconnected patterns, not the linear list of book pages, to guide you through the material.
请在我们的网站上查找本书的配套信息以及有关企业集成的相关信息:www.enterpriseintegrationpatterns.com。您还可以通过电子邮件将您的意见、建议和反馈发送给我们:authors@enterpriseintegrationpatterns.com。
Please look for companion information to this book plus related information on enterprise integration at our Web site: www.enterpriseintegrationpatterns.com. You can also e-mail your comments, suggestions, and feedback to us at authors@enterpriseintegrationpatterns.com.
您现在应该很好地理解了以下概念,它们是本书材料的基础:
You should now have a good understanding of the following concepts, which are fundamental to the material in this book:
什么是消息传递。
What messaging is.
什么是消息系统。
What a messaging system is.
为什么要使用消息传递。
Why to use messaging.
异步编程与同步编程有何不同。
How asynchronous programming is different from synchronous programming.
应用程序集成与应用程序分发有何不同。
How application integration is different from application distribution.
哪些类型的商业产品包含消息传递系统。
What types of commercial products contain messaging systems.
您还应该了解本书将如何教您使用消息传递:
You should also have a feel for how this book is going to teach you to use messaging:
模式在构建材料中的作用。
The role patterns have in structuring the material.
图表中使用的自定义符号的含义。
The meaning of the custom notation used in the diagrams.
示例的目的和范围。
The purpose and scope of the examples.
材料的组织。
The organization of the material.
如何开始学习该材料。
How to get started learning the material.
现在您已经了解了基本概念以及材料的呈现方式,我们邀请您开始学习使用消息传递的企业集成。
Now that you understand the basic concepts and how the material will be presented, we invite you to start learning about enterprise integration using messaging.
本章说明了如何使用本书中的模式来解决各种集成问题。为此,我们研究了常见的集成场景并提供了一个全面的集成示例。当我们设计此示例的解决方案时,我们使用本书中包含的模式来表达该解决方案。在本章结束时,您将熟悉大约两打集成模式。
This chapter illustrates how the patterns in this book can be used to solve a variety of integration problems. In order to do so, we examine common integration scenarios and present a comprehensive integration example. As we design the solution to this example, we express the solution using the patterns contained in this book. At the end of this chapter you will be familiar with about two dozen integration patterns.
企业通常由数百个(如果不是数千个)应用程序组成,这些应用程序是定制的、从第三方获取的、遗留系统的一部分或其组合,在不同操作系统平台的多层中运行。拥有 30 个不同网站、三个 SAP 实例和无数部门解决方案的企业并不罕见。
Enterprises are typically comprised of hundreds, if not thousands, of applications that are custom built, acquired from a third party, part of a legacy system, or a combination thereof, operating in multiple tiers of different operating system platforms. It is not uncommon to find an enterprise that has 30 different Web sites, three instances of SAP, and countless departmental solutions.
我们可能会想问:企业如何让自己陷入如此混乱的境地?难道不应该解雇负责这种企业意大利式架构的 CIO 吗?嗯,就像在大多数情况下一样,事情的发生都是有原因的。
We may be tempted to ask: How do businesses allow themselves to get into such a mess? Shouldn't any CIO who is responsible for such an enterprise spaghetti architecture be fired? Well, as in most cases, things happen for a reason.
首先,编写业务应用程序很困难。创建一个单一的大型应用程序来运行完整的业务几乎是不可能的。企业资源规划 (ERP) 供应商在创建比以往更大的业务应用程序方面取得了一些成功。但现实是,即使是 SAP、Oracle、Peoplesoft 等重量级企业也只能执行典型企业所需业务功能的一小部分。通过 ERP 系统是当今企业中最流行的集成点之一,我们可以很容易地看出这一点。
First, writing business applications is hard. Creating a single, big application to run a complete business is next to impossible. The Enterprise Resource Planning (ERP) vendors have had some success at creating larger-than-ever business applications. The reality, though, is that even the heavyweights like SAP, Oracle, Peoplesoft, and the like perform only a fraction of the business functions required in a typical enterprise. We can see this easily by the fact that ERP systems are one of the most popular integration points in today's enterprises.
其次,将业务功能分散到多个应用程序中,使企业能够根据其需求灵活地选择“最佳”会计软件包、“最佳”客户关系管理软件以及“最佳”订单处理系统。通常,IT 组织对完成所有工作的单个企业应用程序不感兴趣,考虑到单个业务需求的数量,这样的应用程序也是不可能的。
Second, spreading business functions across multiple applications provides the business with the flexibility to select the "best" accounting package, the "best" customer relationship management software, as well as the "best" order-processing system for its needs. Usually, IT organizations are not interested in a single enterprise application that does it all, nor is such an application possible given the number of individual business requirements.
供应商已经学会迎合这种偏好,并围绕特定的核心功能提供有针对性的应用程序。然而,向现有软件包添加新功能的强烈愿望导致了打包业务应用程序之间的一些功能溢出。例如,许多计费系统开始纳入客户服务和会计功能。同样,客户服务软件制造商尝试实施简单的计费功能,例如争议或调整。在系统之间定义清晰的功能分离很困难:客户对账单的争议是否被视为客户服务或计费功能?
Vendors have learned to cater to this preference and offer focused applications around a specific core function. However, the ever-present urge to add new functionality to existing software packages has caused some functionality spillover among packaged business applications. For example, many billing systems started to incorporate customer care and accounting functionality. Likewise, the customer care software maker takes a stab at implementing simple billing functions, such as disputes or adjustments. Defining a clear, functional separation between systems is difficult: Is a customer dispute over a bill considered a customer care or a billing function?
客户、业务合作伙伴和内部用户等用户在与业务交互时通常不会考虑系统边界。他们执行业务功能,无论业务功能跨越多少个内部系统。例如,客户可以致电更改他或她的地址并查看是否收到最后一笔付款。在许多企业中,这个简单的请求可以跨越客户服务和计费系统。同样,客户下新订单可能需要许多系统的协调。企业需要验证客户 ID、验证客户的良好信誉、检查库存、履行订单、获取运输报价、计算销售税、发送账单等。此过程可以轻松跨越五个或六个不同的系统。
Users such as customers, business partners, and internal users generally do not think about system boundaries when they interact with a business. They execute business functions regardless of how many internal systems the business function cuts across. For example, a customer may call to change his or her address and see whether the last payment was received. In many enterprises, this simple request can span across both the customer care and billing systems. Likewise, a customer placing a new order may require the coordination of many systems. The business needs to validate the customer ID, verify the customer's good standing, check inventory, fulfill the order, get a shipping quote, compute sales tax, send a bill, and so on. This process can easily span five or six different systems. From the customer's perspective, it is a single business transaction.
为了支持通用业务流程和跨应用程序的数据共享,需要集成这些应用程序。应用集成需要在多个企业应用之间提供高效、可靠、安全的数据交换。
In order to support common business processes and data sharing across applications, these applications need to be integrated. Application integration needs to provide efficient, reliable, and secure data exchange between multiple enterprise applications.
不幸的是,企业集成并不是一件容易的事。根据定义,企业集成必须处理在不同位置的多个平台上运行的多个应用程序,这使得“简单集成”一词几乎是一个矛盾的说法。软件供应商提供企业应用程序集成(EAI)套件,该套件提供跨平台、跨语言集成以及与许多流行的打包业务应用程序接口的能力。然而,这种技术基础设施仅解决了集成复杂性的一小部分。集成的真正挑战涉及业务和技术问题。
Unfortunately, enterprise integration is no easy task. By definition, enterprise integration has to deal with multiple applications running on multiple platforms in different locations, making the term simple integration pretty much an oxymoron. Software vendors offer Enterprise Application Integration (EAI) suites that provide cross-platform, cross-language integration as well as the ability to interface with many popular packaged business applications. However, this technical infrastructure addresses only a small portion of the integration complexities. The true challenges of integration span far across business and technical issues.
企业整合需要企业政治的重大转变。业务应用程序通常专注于特定的功能领域,例如客户关系管理 (CRM)、计费和财务。这似乎是康威著名定律的延伸:“设计系统的组织被迫生产出这些组织通信结构的副本的设计。” 许多 IT 团队都是根据这些相同的职能领域来组织的。成功的企业集成不仅需要在多个计算机系统之间建立通信,而且还需要在集成的企业应用程序中的业务单元和IT部门之间建立通信,
Enterprise integration requires a significant shift in corporate politics. Business applications generally focus on a specific functional area, such as customer relationship management (CRM), billing, and finance. This seems to be an extension of Conway's famous law: "Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations." Many IT groups are organized in alignment with these same functional areas. Successful enterprise integration needs to establish communication not only between multiple computer systems but also between business units and IT departmentsin an integrated enterprise application, groups no longer control a specific application because each application is now part of an overall flow of integrated applications and services.
由于其范围广泛,集成工作通常会对业务产生深远的影响。一旦最关键业务功能的处理被纳入集成解决方案中,该解决方案的正常运行对于业务就变得至关重要。失败或行为不当的集成解决方案可能会因订单丢失、付款错误和客户不满而给企业造成数百万美元的损失。
Because of their wide scope, integration efforts typically have far-reaching implications on the business. Once the processing of the most critical business functions is incorporated into an integration solution, the proper functioning of that solution becomes vital to the business. A failing or misbehaving integration solution can cost a business millions of dollars in lost orders, misrouted payments, and disgruntled customers.
开发集成解决方案的一个重要限制是集成开发人员通常对参与的应用程序具有有限的控制能力。在大多数情况下,应用程序是遗留系统或打包应用程序,无法仅为了连接到集成解决方案而进行更改。这通常使集成开发人员处于必须弥补应用程序内部的缺陷或特性或应用程序之间的差异的境地。通常,在应用程序端点内实现部分解决方案会更容易,但由于政治或技术原因,该选项可能不可用。
One important constraint of developing integration solutions is the limited amount of control the integration developers typically have over the participating applications. In most cases, the applications are legacy systems or packaged applications that cannot be changed just to be connected to an integration solution. This often leaves the integration developers in a situation where they have to make up for deficiencies or idiosyncrasies inside the applications or differences between the applications. Often it would be easier to implement part of the solution inside the application endpoints, but for political or technical reasons, that option may not be available.
尽管对集成解决方案的需求广泛,但只有少数标准在该领域建立起来。XML、XSL 和 Web 服务的出现无疑标志着集成解决方案中基于标准的功能的最重大进步。然而,围绕 Web 服务的炒作也为市场新的碎片化提供了基础,导致一系列新的标准“扩展”和“解释”。这应该提醒我们,“符合标准”的产品之间缺乏互操作性是 CORBA 的主要障碍之一,而 CORBA 为系统集成提供了复杂的技术解决方案。
Despite the widespread need for integration solutions, only a few standards have established themselves in this domain. The advent of XML, XSL, and Web services certainly marks the most significant advance of standards-based features in an integration solution. However, the hype around Web services has also given grounds to new fragmentation of the marketplace, resulting in a flurry of new "extensions" and "interpretations" of the standards. This should remind us that the lack of interoperability between "standards-compliant" products was one of the major stumbling blocks for CORBA, which offered a sophisticated technical solution for systems integration.
现有的 XML Web 服务标准仅解决了集成挑战的一小部分。例如,经常声称 XML 是系统集成的通用语言就有些误导。将所有数据交换标准化为 XML 可以比作使用通用字母表(例如罗马字母表)编写所有文档。尽管字母表很常见,但它仍然被用来表示许多语言和方言,而这些语言和方言并不是所有读者都能轻易理解的。企业整合也是如此。通用表示(例如 XML)的存在并不意味着通用语义。“账户”的概念在每个参与系统中可以有许多不同的语义、内涵、约束和假设。
Existing XML Web services standards address only a fraction of the integration challenges. For example, the frequent claim that XML is the lingua franca of system integration is somewhat misleading. Standardizing all data exchange to XML can be likened to writing all documents using a common alphabet, such as the Roman alphabet. Even though the alphabet is common, it is still being used to represent many languages and dialects, which cannot be readily understood by all readers. The same is true in enterprise integration. The existence of a common presentation (e.g., XML) does not imply common semantics. The notion of "account" can have many different semantics, connotations, constraints, and assumptions in each participating system. Resolving semantic differences between systems proves to be a particularly difficult and time-consuming task because it involves significant business and technical decisions.
虽然开发 EAI 解决方案本身具有挑战性,但操作和维护此类解决方案可能更加艰巨。EAI 解决方案的技术组合和分布式特性使得部署、监控和故障排除任务变得复杂,需要多种技能组合。在大多数情况下,这些技能集分布在许多不同的个人中,或者甚至不存在于 IT 运营中。
While developing an EAI solution is challenging in itself, operating and maintaining such a solution can be even more daunting. The mix of technologies and the distributed nature of EAI solutions make deployment, monitoring, and troubleshooting complex tasks that require a combination of skill sets. In most cases, these skill sets are spread across many different individuals or do not even exist within IT operations.
任何经历过 EAI 部署的人都可以证明,EAI 解决方案是当今企业战略的关键组成部分,但它们使 IT 的生活变得更加困难,而不是更轻松。集成企业的高级愿景(由直通式处理、T+1、敏捷企业等术语定义)和具体实施(System.Messaging.XmlMessageFormatter 的参数是什么)之间还有很长的路要走。再拿一次?)。
Anyone who has been through an EAI deployment can attest that EAI solutions are a critical component of today's enterprise strategiesbut they make IT life harder, not easier. It's a long way between the high-level vision of the integrated enterprise (defined by terms such as straight-through-processing, T+1, agile enterprise) and the nuts-and-bolts implementations (What parameters did System.Messaging.XmlMessageFormatter take again?).
企业集成没有简单的答案。在我们看来,任何声称集成很容易的人一定非常聪明(或者至少比我们其他人聪明一点),非常无知(好吧,让我们说乐观),或者有经济利益让你相信集成很容易。
There are no simple answers for enterprise integration. In our opinion, anyone who claims that integration is easy must be incredibly smart (or at least a good bit smarter than the rest of us), incredibly ignorant (okay, let's say optimistic), or have a financial interest in making you believe that integration is easy.
尽管整合是一个广泛而困难的话题,但我们总是可以观察到一些人在这方面比其他人做得更好。这些人知道哪些其他人不知道的事情?由于没有“21 天自学整合”这样的东西(这本书肯定不是!),这些人不太可能知道整合的所有答案。然而,他们通常已经解决了足够多的集成问题,可以将新问题与之前解决的问题进行比较。他们知道问题的“模式”和相关的解决方案。随着时间的推移,他们通过反复试验或从其他经验丰富的集成架构师那里学习了这些模式。
Even though integration is a broad and difficult topic, we can always observe some people who are much better at it than others. What do these people know that others don't? Since there is no such thing as "Teach Yourself Integration in 21 Days" (this book sure ain't!), it is unlikely that these people know all the answers to integration. However, they have usually solved enough integration problems that they can compare new problems to prior problems they have solved. They know the "patterns" of problems and associated solutions. They learned these patterns over time by trial-and-error or from other experienced integration architects.
这些模式不是复制粘贴代码示例或收缩包装的组件,而是描述经常出现的问题的解决方案的建议。如果使用得当,集成模式可以帮助填补集成的高级愿景与实际系统实现之间的巨大差距。
The patterns are not copy-paste code samples or shrink-wrapped components, but rather nuggets of advice that describe solutions to frequently recurring problems. Used properly, the integration patterns can help fill the wide gap between the high-level vision of integration and the actual system implementation.
我们有意将集成的定义保留得非常广泛。对我们来说,这意味着连接计算机系统、公司或人员。虽然这个广泛的定义使我们可以方便地将我们感兴趣的任何内容放入本书中,但仔细研究一些最常见的集成场景会很有帮助。在许多集成项目的过程中,我们反复遇到以下六种类型的集成:
We intentionally left the definition of integration very broad. To us it means connecting computer systems, companies, or people. While this broad definition gives us the convenience of sticking whatever we find interesting into this book, it is helpful to have a closer look at some of the most common integration scenarios. Over the course of many integration projects, we repeatedly came across the following six types of integration:
信息门户
Information portals
数据复制
Data replication
共享业务功能
Shared business functions
面向服务的架构
Service-oriented architectures
分布式业务流程
Distributed business processes
企业对企业整合
Business-to-business integration
此列表绝不是所有集成的完整分类,但它确实说明了集成架构师构建的解决方案类型。许多集成项目由多种类型的集成组合而成。例如,通常需要复制参考数据,以便将应用程序绑定到单个分布式业务流程中。
This list is by no means a complete taxonomy of all things integration, but it does illustrate the kind of solutions that integration architects build. Many integration projects consist of a combination of multiple types of integration. For example, reference data replication is often required in order to tie applications into a single, distributed business process.
信息门户
Information Portal
许多业务用户必须访问多个系统才能回答特定问题或执行一项业务功能。例如,为了验证订单的状态,客户服务代表可能必须访问大型机上的订单管理系统并登录到管理通过 Web 下达的订单的系统。信息门户将来自多个源的信息聚合到单个显示中,以避免用户访问多个系统来获取信息。简单的信息门户将屏幕划分为多个区域,每个区域显示来自不同系统的信息。更复杂的系统在区域之间提供有限的交互;例如,当用户从区域 A 的列表中选择一个项目时,区域 B 刷新并显示有关所选项目的详细信息。其他门户提供更复杂的用户交互,并模糊了门户和集成应用程序之间的界限。
Many business users have to access more than one system to answer a specific question or to perform a single business function. For example, to verify the status of an order, a customer service representative may have to access the order management system on the mainframe plus log on to the system that manages orders placed over the Web. Information portals aggregate information from multiple sources into a single display to avoid having the user access multiple systems for information. Simple information portals divide the screen into multiple zones, each of which displays information from a different system. More sophisticated systems provide limited interaction between zones; for example, when a user selects an item from a list in zone A, zone B refreshes with detailed information about the selected item. Other portals provide even more sophisticated user interaction and blur the line between a portal and an integrated application.
数据复制
Data Replication
许多业务系统需要访问相同的数据。例如,客户的地址可能会用在客户服务系统(当客户致电更改地址时)、会计系统(计算销售税)、运输系统(为货物贴上标签)和计费系统(以发送发票)。其中许多系统都有自己的数据存储来存储与客户相关的信息。当客户致电更改其地址时,所有这些系统都需要更改其客户地址的副本。这可以通过实施基于数据复制的集成策略来实现。
Many business systems require access to the same data. For example, a customer's address may be used in the customer care system (when the customer calls to change it), the accounting system (to compute sales tax), the shipping system (to label the shipment), and the billing system (to send an invoice). Many of these systems have their own data stores to store customer-related information. When a customer calls to change his or her address, all these systems need to change their copy of the customer's address. This can be accomplished by implementing an integration strategy based on data replication.
有许多不同的方法来实现数据复制。例如,一些数据库供应商在数据库中内置了复制功能;或者,我们可以将数据导出到文件中,然后将它们重新导入到其他系统,或者我们可以使用面向消息的中间件来传输消息内的数据记录。
There are many different ways to implement data replication. For example, some database vendors build replication functions into the database; alternatively, we can export data into files and re-import them to the other system, or we can use message-oriented middleware to transport data records inside messages.
共享业务功能
Shared Business Function
就像许多业务应用程序存储冗余数据一样,它们也倾向于实现冗余功能。多个系统可能需要检查社会安全号码是否有效、地址是否与指定的邮政编码匹配,或者特定商品是否有库存。将这些功能公开为共享业务功能是有意义的,该功能只需实现一次即可作为服务提供给其他系统。
In the same way that many business applications store redundant data, they also tend to implement redundant functionality. Multiple systems may need to check whether a social-security number is valid, whether the address matches the specified postal code, or whether a particular item is in stock. It makes sense to expose these functions as a shared business function that is implemented once and available as a service to other systems.
共享业务功能可以满足一些与数据复制相同的需求。例如,我们可以实现一个名为“获取客户地址”的业务功能,该功能允许其他系统在需要时请求客户的地址,而不是永久存储冗余副本。这两种方法之间的决定是由许多标准驱动的,例如我们对系统的控制量(调用共享函数通常比将数据加载到数据库更具侵入性)或变化率(地址可能会改变)。经常需要但很少改变)。
A shared business function can address some of the same needs as data replication. For example, we could implement a business function called Get Customer Address that could allow other systems to request the customer's address when it is needed rather than permanently store a redundant copy. The decision between these two approaches is driven by a number of criteria, such as the amount of control we have over the systems (calling a shared function is usually more intrusive than loading data into the database) or the rate of change (an address may be needed frequently but change very infrequently).
面向服务的架构
Service-Oriented Architecture
共享业务功能通常称为服务。服务是一种定义明确的功能,普遍可用并响应“服务消费者”的请求。一旦企业组装了有用服务的集合,管理服务就成为一项关键功能。首先,应用程序需要某种形式的服务目录,即所有可用服务的集中列表。其次,每个服务需要以应用程序可以与服务“协商”通信合同的方式描述其接口。服务发现和协商这两个功能是构成面向服务的体系结构 (SOA) 的关键要素。
Shared business functions are often referred to as services. A service is a well-defined function that is universally available and responds to requests from "service consumers." Once an enterprise assembles a collection of useful services, managing the services becomes a critical function. First, applications need some form of service directory, a centralized list of all available services. Second, each service needs to describe its interface in such a way that an application can "negotiate" a communications contract with the service. These two functions, service discovery and negotiation, are the key elements that make up a service-oriented architecture (SOA).
SOA 模糊了集成和分布式应用程序之间的界限。可以使用其他应用程序提供的现有远程服务来开发新的应用程序。因此,调用服务可以认为是两个应用程序之间的集成。然而,大多数 SOA 提供的工具使调用外部服务几乎与调用本地方法一样简单(抛开性能考虑),因此在 SOA 之上开发应用程序的过程类似于构建分布式应用程序。
SOAs blur the line between integration and distributed applications. A new application can be developed using existing remote services that may be provided by other applications. Therefore, calling a service can be considered integration between the two applications. However, most SOAs provide tools that make calling an external service almost as simple as calling a local method (performance considerations aside), so that the process of developing an application on top of an SOA resembles building a distributed application.
分布式业务流程
Distributed Business Process
集成的关键驱动因素之一是单个业务事务通常分布在许多不同的系统中。前面的例子向我们展示了一个简单的业务功能(例如下订单)可以轻松地触及六个系统。在大多数情况下,所有相关功能都包含在现有应用程序中。缺少的是这些应用程序之间的协调。因此,我们可以添加一个业务流程管理组件来管理跨多个现有系统的业务功能的执行。
One of the key drivers of integration is that a single business transaction is often spread across many different systems. A previous example showed us that a simple business function such as placing an order can easily touch half a dozen systems. In most cases, all relevant functions are incorporated inside existing applications. What is missing is the coordination between these applications. Therefore, we can add a business process management component that manages the execution of a business function across multiple existing systems.
SOA 和分布式业务之间的界限可能是模糊的。例如,您可以将所有相关业务功能公开为服务,然后在通过 SOA 访问所有服务的应用程序内对业务流程进行编码。
The boundaries between an SOA and a distributed business can be fuzzy. For example, you could expose all relevant business functions as services and then encode the business process inside an application that accesses all services via an SOA.
企业对企业整合
Business-to-Business Integration
到目前为止,我们主要考虑的是单个企业内部应用程序和业务功能之间的交互。在许多情况下,业务功能可以从外部供应商或业务合作伙伴处获得。例如,运输公司可以为客户提供计算运输成本或跟踪运输的服务。或者,企业可以使用外部提供商来计算销售税率。同样,业务合作伙伴之间也经常发生整合。顾客可以联系零售商询问商品的价格和库存情况。作为回应,零售商可以向供应商询问包含缺货商品的预期发货的状态。
So far, we have mainly considered the interaction between applications and business functions inside a single enterprise. In many cases, business functions may be available from outside suppliers or business partners. For example, the shipping company may provide a service for customers to compute shipping cost or track shipments. Or a business may use an outside provider to compute sales tax rates. Likewise, integration frequently occurs between business partners. A customer may contact a retailer to inquire on the price and availability of an item. In response, the retailer may ask the supplier for the status of an expected shipment that contains the out-of-stock item.
上述许多考虑因素同样适用于企业对企业的集成。然而,通过互联网或其他网络进行通信通常会引发与传输协议和安全性相关的新问题。此外,由于许多业务合作伙伴可能在电子“对话”中进行协作,因此标准化数据格式至关重要。
Many of the above considerations apply equally to business-to-business integration. However, communicating across the Internet or some other network usually raises new issues related to transport protocols and security. Also, since many business partners may collaborate in an electronic "conversation," standardized data formats are critically important.
企业架构和集成中最流行的术语之一是松耦合。事实上,这是一个非常流行的术语,以至于 Doug Kaye 写了一整本书,以这个无处不在的概念为标题 [ Kaye ]。松耦合的好处早已为人所知,但由于 Web 服务架构的迅速普及,它们最近才成为人们关注的焦点。
One of the biggest buzzwords in enterprise architecture and integration is loose coupling. It is in fact such a popular term that Doug Kaye wrote a whole book titled after this ubiquitous concept [Kaye]. The benefits of loose coupling have been known for quite some time now, but they have taken center stage more recently due to the surging popularity of Web services architectures.
松耦合背后的核心原则是减少两方(组件、应用程序、服务、程序、用户)在交换信息时对彼此所做的假设。两方对彼此和通用协议做出的假设越多,通信的效率就越高,但解决方案对中断或更改的容忍度就越低,因为双方彼此紧密耦合。
The core principle behind loose coupling is to reduce the assumptions two parties (components, applications, services, programs, users) make about each other when they exchange information. The more assumptions two parties make about each other and the common protocol, the more efficient the communication can be, but the less tolerant the solution is of interruptions or changes because the parties are tightly coupled to each other.
紧耦合的一个很好的例子是本地方法调用。在应用程序内调用本地方法基于被调用例程和调用例程之间的许多假设。两种方法都必须在同一进程(例如虚拟机)中运行并以相同语言编写(或至少使用通用中间语言或字节代码)。调用方法必须使用商定的数据类型传递确切数量的预期参数。通话是即时的;也就是说,被调用方法在调用方法调用后立即开始处理。同时,只有当被调用方法完成时,调用方法才会恢复处理(意味着调用是同步的)。处理将在调用方法中自动恢复,并紧随方法调用后的语句。方法之间的通信是立即且瞬时的,因此调用者和被调用方法都不必担心第三方窃听形式的安全漏洞。所有这些假设使得编写结构良好的应用程序变得非常容易,这些应用程序将功能划分为单独的方法以供其他方法调用。由此产生的大量小方法可以实现灵活性和重用。所有这些假设使得编写结构良好的应用程序变得非常容易,这些应用程序将功能划分为单独的方法以供其他方法调用。由此产生的大量小方法可以实现灵活性和重用。所有这些假设使得编写结构良好的应用程序变得非常容易,这些应用程序将功能划分为单独的方法以供其他方法调用。由此产生的大量小方法可以实现灵活性和重用。
A great example of tight coupling is a local method invocation. Invoking a local method inside an application is based on a lot of assumptions between the called and the calling routine. Both methods have to run in the same process (e.g., a virtual machine) and be written in the same language (or at least use a common intermediate language or byte code). The calling method has to pass the exact number of expected parameters using agreed-upon data types. The call is immediate; that is, the called method starts processing immediately after the calling method makes the call. Meanwhile, the calling method will resume processing only when the called method completes (meaning the invocation is synchronous). Processing will automatically resume in the calling method with the statement immediately following the method call. The communication between the methods is immediate and instantaneous, so neither the caller nor the called method has to worry about security breaches in the form of eavesdropping third parties. All these assumptions make it very easy to write well-structured applications that divide functionality into individual methods to be called by other methods. The resulting large number of small methods allows for flexibility and reuse.
许多集成方法旨在通过将远程数据交换封装成与本地方法调用相同的语义来使远程通信变得简单。这种策略产生了远程过程调用(RPC)或远程方法调用(RMI)的概念,许多流行的框架和平台都支持这种概念:CORBA(参见 [ Zahavi])、Microsoft DCOM、.NET Remoting、Java RMI 以及最近的 RPC 风格的 Web 服务。这种方法的预期好处是双重的。首先,同步方法调用语义对于应用程序开发人员来说非常熟悉,那么为什么不建立在我们已经知道的基础上呢?其次,对本地方法调用和远程调用使用相同的语法和语义将使我们能够将哪些组件应在本地运行、哪些组件应远程运行的决定推迟到部署时,从而使应用程序开发人员少担心一件事。
Many integration approaches have aimed to make remote communications simple by packaging a remote data exchange into the same semantics as a local method call. This strategy resulted in the notion of a Remote Procedure Call (RPC) or Remote Method Invocation (RMI), supported by many popular frameworks and platforms: CORBA (see [Zahavi]), Microsoft DCOM, .NET Remoting, Java RMI, and most recently, RPC-style Web services. The intended upside of this approach is twofold. First, synchronous method-call semantics are very familiar to application developers, so why not build on what we already know? Second, using the same syntax and semantics for both local method calls and remote invocations would allow us to defer until deployment time the decision about which components should run locally and which should run remotely, leaving the application developer with one less thing to worry about.
所有这些方法面临的挑战在于远程通信使本地方法调用所基于的许多假设无效。因此,将远程通信抽象为方法调用的简单语义可能会造成混乱和误导。Waldo 和同事早在 1994 年就提醒我们,“在分布式系统中交互的对象需要以与在单个地址空间中交互的对象本质上不同的方式进行处理”[ Waldo]。例如,如果我们调用远程服务来为我们执行某个功能,我们真的想将自己限制为仅使用与我们使用的相同编程语言构建的那些服务吗?通过网络进行的呼叫往往也比本地呼叫慢多个数量级。调用方法真的应该等到被调用方法完成吗?如果网络中断,调用的方法暂时无法访问怎么办?我们应该等多久?我们如何确保我们与目标方而不是第三方“欺骗者”进行通信?我们如何防止窃听?如果被调用方法的方法签名(预期参数列表)发生变化怎么办?如果远程方法由第三方或业务合作伙伴维护,我们不再能够控制此类变化。我们应该让我们的方法调用失败,还是应该尝试找到参数之间的最佳可能映射并仍然进行调用?很快就会发现,远程集成带来了许多本地方法调用从未处理过的问题。
The challenge that all these approaches face lies in the fact that remote communication invalidates many of the assumptions that a local method call is based on. As a result, abstracting the remote communication into the simple semantics of a method call can be confusing and misleading. Waldo and colleagues reminded us back in 1994 that "objects that interact in a distributed system need to be dealt with in ways that are intrinsically different from objects that interact in a single address space" [Waldo]. For example, if we call a remote service to perform a function for us, do we really want to restrict ourselves to only those services that were built using the same programming language we use? A call across the network also tends to be multiple orders of magnitude slower than a local call. Should the calling method really wait until the called method completes? What if the network is interrupted and the called method is temporarily unreachable? How long should we wait? How can we be sure we communicate with the intended party and not a third-party "spoofer"? How can we protect against eavesdropping? What if the method signature (the list of expected parameters) of the called method changes? If the remote method is maintained by a third party or a business partner, we no longer have control over such changes. Should we have our method invocation fail, or should we attempt to find the best possible mapping between the parameters and still make the call? It quickly becomes apparent that remote integration brings up a lot of issues that a local method call never had to deal with.
总之,试图将远程通信描述为本地方法调用的变体是自找麻烦。这种紧密耦合的架构通常会导致解决方案脆弱、难以维护且可扩展性差。许多 Web 服务先驱最近(重新)艰难地发现了这一事实。
In summary, trying to portray remote communication as a variant of a local method invocation is asking for trouble. Such tightly coupled architectures typically result in brittle, hard-to-maintain, and poorly scalable solutions. Many Web services pioneers recently (re-)discovered this fact the hard way.
为了展示紧密耦合依赖关系的影响以及如何解决它们,让我们看一下连接两个系统的非常简单的方法。假设我们正在构建一个网上银行系统,允许客户从另一家银行将钱存入他们的帐户。为了执行此功能,前端 Web 应用程序必须与管理资金转移的后端财务系统集成。
To show the effects of tightly coupled dependencies and how to resolve them, let's look at a very simple way of connecting two systems. Let's assume we are building an online banking system that allows customers to deposit money into their account from another bank. To perform this function, the front-end Web application has to be integrated with the back-end financial system that manages fund transfers.
连接两个系统最简单的方法是通过 TCP/IP 协议。过去 15 年中创建的每一个有自尊的操作系统或编程库都肯定包含 TCP/IP 堆栈。TCP/IP 是一种普遍存在的通信协议,可在连接到 Internet 和本地网络的数百万台计算机之间传输数据。为什么不使用最普遍的网络协议在两个应用程序之间进行通信?
The easiest way to connect the two systems is through the TCP/IP protocol. Every self-respecting operating system or programming library created in the last 15 years is certain to include a TCP/IP stack. TCP/IP is the ubiquitous communications protocol that transports data between the millions of computers connected to the Internet and local networks. Why not use the most ubiquitous of all network protocols to communicate between two applications?
为了简单起见,我们假设将钱存入某人帐户的远程函数仅使用此人的姓名和美元金额作为参数。以下几行代码足以通过 TCP/IP 调用此类函数(我们选择了 C#,但此代码在 C 或 Java 中看起来几乎相同)。
To keep things simple, let's assume that the remote function that deposits money into a person's account takes only the person's name and the dollar amount as arguments. The following few lines of code suffice to call such a function over TCP/IP (we chose C#, but this code would look virtually identical in C or Java).
String 主机名 = "finance.bank.com";
国际端口=80;
IPHostEntry 主机信息 = Dns.GetHostByName(主机名);
IPAddress 地址 = hostInfo.AddressList[0];
IPEndPoint 端点 = new IPEndPoint(地址, 端口);
Socket套接字 = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
套接字.Connect(端点);
byte[] 金额 = BitConverter.GetBytes(1000);
byte[] name = Encoding.ASCII.GetBytes("Joe");
int bytesSent = socket.Send(金额);
bytesSent += socket.Send(名称);
套接字.关闭();
String hostName = "finance.bank.com";
int port = 80;
IPHostEntry hostInfo = Dns.GetHostByName(hostName);
IPAddress address = hostInfo.AddressList[0];
IPEndPoint endpoint = new IPEndPoint(address, port);
Socket socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(endpoint);
byte[] amount = BitConverter.GetBytes(1000);
byte[] name = Encoding.ASCII.GetBytes("Joe");
int bytesSent = socket.Send(amount);
bytesSent += socket.Send(name);
socket.Close();
此代码打开到地址Finance.bank.com 的套接字连接,并通过网络发送两个数据项(金额和客户姓名)。不需要昂贵的中间件:不需要EAI工具,RPC工具包只需10行代码。当我们运行此代码时,它告诉我们“已发送 7 个字节”。瞧!整合怎么会那么困难呢?
This code opens a socket connection to the address finance.bank.com and sends two data items (the amount and the customer's name) across the network. No expensive middleware is required: no EAI tools, RPC toolkitsjust 10 lines of code. When we run this code, it tells us "7 bytes sent." Voila! How can integration be difficult?
这种集成尝试存在几个主要问题。TCP/IP 协议的优势之一是其广泛的支持,因此我们可以连接到几乎任何连接到网络的计算机,无论其使用什么操作系统或编程语言。然而,平台无关性仅适用于非常简单的消息:字节流。为了将数据转换为字节流,我们使用了BitConverter班级。此类使用数据类型的内部存储器表示形式将任何数据类型转换为字节数组。问题是整数的内部表示因计算机系统而异。例如,.NET 使用 32 位整数,而其他系统可能使用 64 位表示。我们的示例通过网络传输 4 个字节来表示 32 位整数。使用 64 位的系统倾向于从网络读取 8 个字节,最终将整个消息(包括客户名称)解释为单个数字。
There are a couple of major problems with this integration attempt. One of the strengths of the TCP/IP protocol is its wide support so that we can connect to pretty much any computer connected to the network regardless of the operating system or programming language it uses. However, the platform-independence works only for very simple messages: byte streams. In order to convert our data into a byte stream, we used the BitConverter class. This class converts any data type into a byte array, using the internal memory representation of the data type. The catch is that the internal representation of an integer number varies with computer systems. For example, .NET uses a 32-bit integer, while other systems may use a 64-bit representation. Our example transfers 4 bytes across the network to represent a 32-bit integer number. A system using 64 bits would be inclined to read 8 bytes off the network and would end up interpreting the whole message (including the customer name) as a single number.
此外,一些计算机系统以大端格式存储数字,而另一些计算机系统以小端格式存储数字。大端格式首先存储从最高字节开始的数字,而小端系统首先存储最低字节。PC 采用小端模式运行,因此代码通过网络传递以下 4 个字节:
Also, some computer systems store their numbers in big-endian format, while others store them in little-endian format. A big-endian format stores numbers starting with the highest byte first, while little-endian systems store the lowest byte first. PCs operate on a little-endian scheme so that the code passes the following 4 bytes across the network:
232 3 0 0
232 3 0 0
232 + 3 * 2 8等于 1,000。使用大端数字的系统会认为此消息表示 232 * 2 24 + 3 * 2 16 = 3,892,510,720。乔将成为一个非常富有的人!因此,这种方法仅在所有连接的计算机以相同内部格式表示数字的假设下才有效。
232 + 3 * 28 equals 1,000. A system that uses big-endian numbers would consider this message to mean 232 * 224 + 3 * 216 = 3,892,510,720. Joe will be a very rich man! So this approach works only under the assumption that all connected computers represent numbers in the same internal format.
这种简单方法的第二个问题是我们指定远程计算机的位置(在我们的例子中为finance.bank.com)。动态命名服务 (DNS) 为我们提供了域名和 IP 地址之间的一级间接寻址,但如果我们想将该功能移至不同域中的另一台计算机上该怎么办?如果机器出现故障并且我们必须设置另一台机器怎么办?如果我们想将信息发送到多台机器怎么办?对于每种情况,我们都必须更改代码。如果我们使用很多远程功能,这可能会变得非常乏味。因此,我们应该找到一种方法,使我们的通信独立于网络上的特定机器。
The second problem with this simple approach is that we specify the location of the remote machine (in our case, finance.bank.com). The Dynamic Naming Service (DNS) gives us one level of indirection between the domain name and the IP address, but what if we want to move the function to a different computer on a different domain? What if the machine fails and we have to set up another machine? What if we want to send the information to more than one machine? For each scenario, we would have to change the code. If we use a lot of remote functions, this could become very tedious. So, we should find a way to make our communication independent from a specific machine on the network.
我们的简单 TCP/IP 示例还在两台机器之间建立了时间依赖性。TCP/IP 是一种面向连接的协议。在传输任何数据之前,必须首先建立连接。建立 TCP 连接涉及 IP 数据包在发送方和接收方之间来回传输。这就要求机器和网络同时可用。如果这三个部分中的任何一个出现故障或由于高负载而无法使用,则无法发送数据。
Our simple TCP/IP example also establishes temporal dependencies between the two machines. TCP/IP is a connection-oriented protocol. Before any data can be transferred, a connection has to be established first. Establishing a TCP connection involves IP packets traveling back and forth between sender and receiver. This requires that both machines and the network are all available at the same time. If any of the three pieces is malfunctioning or not available due to high load, the data cannot be sent.
最后,简单的通信还依赖于非常严格的数据格式。我们发送 4 个字节的金额数据,然后发送定义客户帐户的字符序列。如果我们想插入第三个参数,例如货币名称,我们必须修改发送者和接收者以使用新的数据格式。
Finally, the simple communication also relies on a very strict data format. We are sending 4 bytes of amount data and then a sequence of characters that define the customer's account. If we want to insert a third parameter, such as the name of the currency, we would have to modify both sender and receiver to use the new data format.
紧耦合交互
Tightly Coupled Interaction
我们的极简集成解决方案既快速又便宜,但它会导致一个非常脆弱的解决方案,因为两个参与方对彼此做出以下假设:
Our minimalist integration solution is fast and cheap, but it results in a very brittle solution because the two participating parties make the following assumptions about each other:
数字和对象的平台技术 内部表示
Platform technology internal representations of numbers and objects
位置 硬编码机器地址
Location hardcoded machine addresses
所有组件必须同时可用的时间
Time all components have to be available at the same time
数据格式参数 列表及其类型必须匹配
Data format the list of parameters and their types must match
正如我们之前所说,耦合是衡量各方在沟通时对彼此做出多少假设的指标。我们的简单解决方案需要各方做出很多假设。因此,该解决方案是紧耦合的。
As we stated earlier, coupling is a measure of how many assumptions parties make about each other when they communicate. Our simple solution requires the parties to make a lot of assumptions. Therefore, this solution is tightly coupled.
为了使解决方案更加松散耦合,我们可以尝试将这些依赖项一一删除。我们应该使用一种自描述且与平台无关的标准数据格式,例如XML。我们不应该将信息直接发送到特定的机器,而应该将其发送到可寻址的通道。通道是发送者和接收者可以在不知道彼此身份的情况下达成一致的逻辑地址。使用通道可以解决位置依赖性,但如果通道是使用面向连接的协议实现的,则仍然需要所有组件同时可用。为了消除这种时间依赖性,我们可以增强通道以对发送的请求进行排队,直到网络和接收系统准备就绪。发送者现在可以将请求发送到通道中并继续处理,而不必担心数据的传递。在通道内对请求进行排队需要将数据分块为独立的消息以便通道知道在任一时间要缓冲和传送多少数据。这两个系统仍然依赖于通用的数据格式,但我们可以通过允许通道内的数据格式转换来消除这种依赖性。如果一个系统的格式发生变化,我们只需要改变变压器,而不需要改变其他参与的系统。如果许多应用程序将数据发送到同一通道,这尤其有用
In order to make the solution more loosely coupled, we can try to remove these dependencies one by one. We should use a standard data format that is self-describing and platform-independent, such as XML. Instead of sending information directly to a specific machine, we should send it to an addressable channel. A channel is a logical address that both sender and receiver can agree on without being aware of each other's identity. Using channels resolves the location dependency but still requires all components to be available at the same time if the channel is implemented using a connection-oriented protocol. In order to remove this temporal dependency, we can enhance the channel to queue up sent requests until the network and the receiving system are ready. The sender can now send requests into the channel and continue processing without having to worry about the delivery of the data. Queuing requests inside the channel requires data to be chunked into self-contained messages so that the channel knows how much data to buffer and deliver at any one time. The two systems still depend on a common data format, but we can remove this dependency by allowing for data format transformations inside the channel. If the format of one system changes, we only have to change the transformer and not the other participating systems. This is particularly useful if many applications send data to the same channel
松耦合交互
Loosely Coupled Interaction
通用数据格式、跨队列通道的异步通信和变压器等机制有助于将紧密耦合的解决方案转变为松散耦合的解决方案。发送方不再需要依赖接收方的内部数据格式或其位置。它甚至不必关注另一台计算机是否准备好接受请求。消除系统之间的这些依赖关系使整体解决方案更能容忍变化,这是松散耦合的主要好处。松散耦合方法的主要缺点是增加了复杂性。这不再是 10 行代码的解决方案!因此,我们使用面向消息的中间件为我们提供这些服务的基础设施。这种基础设施使得以松散耦合的方式交换数据几乎与我们开始的示例一样简单。下一节将介绍构成此类中间件解决方案的组件。
Mechanisms such as a common data format, asynchronous communication across queuing channels, and transformers help turn a tightly coupled solution into a loosely coupled one. The sender no longer has to depend on the receiver's internal data format nor on its location. It does not even have to pay attention to whether or not the other computer is ready to accept requests. Removing these dependencies between the systems makes the overall solution more tolerant to change, the key benefit of loose coupling. The main drawback of the loosely coupled approach is the additional complexity. This is no longer a 10-lines-of-code solution! Therefore, we use a message-oriented middleware infrastructure that provides these services for us. This infrastructure makes exchanging data in a loosely coupled way almost as easy as the example we started with. The next section describes the components that make up such a middleware solution.
松耦合是万能药吗?与企业架构中的其他事物一样,没有单一的最佳答案。松耦合提供了灵活性和可扩展性等重要优势,但它引入了更复杂的编程模型,并使设计、构建和调试解决方案变得更加困难。
Is loose coupling the panacea? Like everything else in enterprise architecture, there is no single best answer. Loose coupling provides important benefits such as flexibility and scalability, but it introduces a more complex programming model and can make designing, building, and debugging solutions more difficult.
为了通过集成解决方案连接两个系统,必须解决许多问题。这些功能构成了我们所说的中间件,即应用程序之间的粘合剂。
In order to connect two systems via an integration solution, a number of issues have to be resolved. These functions make up what we call middlewarethe glue that sits between applications.
某些数据总是必须从一个应用程序传输到下一个应用程序。该数据可能是需要复制的地址记录、对远程服务的调用或前往门户显示的 HTML 片段。无论有效负载如何,这段数据都需要被两端理解并且需要通过网络传输。两个元件提供了这一基本功能。我们需要一种可以将信息从一个应用程序转移到另一个应用程序的通信渠道。该通道可以由一系列 TCP/IP 连接、共享文件、共享数据库或从一台计算机传送到另一台计算机的软盘(臭名昭著的“sneakernet”)组成。在此频道内,我们放置一条消息对要集成的两个应用程序具有商定含义的数据片段。这条数据可以非常小,例如已更改的单个客户的电话号码,也可以非常大,例如所有客户及其关联地址的完整列表。
Invariably, some data has to be transported from one application to the next. This data could be an address record that needs to be replicated, a call to a remote service, or a snippet of HTML headed for a portal display. Regardless of the payload, this piece of data needs to be understood by both ends and needs to be transported across a network. Two elements provide this basic function. We need a communications channel that can move information from one application to the other. This channel could consist of a series of TCP/IP connections, a shared file, a shared database, or a floppy disk being carried from one computer to the next (the infamous "sneakernet"). Inside this channel, we place a messagea snippet of data that has an agreed-upon meaning to both applications that are to be integrated. This piece of data can be very small, such as the phone number of a single customer that has changed, or it can be very large, such as the complete list of all customers and their associated addresses.
基于消息的集成的基本要素
Basic Elements of Message-Based Integration
现在我们可以跨渠道发送消息,我们可以建立一种非常基本的集成形式。然而,我们承诺简单的集成是一个矛盾的说法,所以让我们看看缺少什么。我们提到集成解决方案通常对其所集成的应用程序具有有限的控制,例如应用程序使用的内部数据格式。例如,一种数据格式可能将客户姓名存储在两个字段中,称为FIRST_NAME和LAST_NAME ,而另一种系统可能使用称为Customer_Name的单个字段。同样,一个系统可能支持多个客户地址,而另一系统仅支持单个地址。由于应用程序的内部数据格式通常无法更改,因此中间件需要提供某种机制来将一个应用程序的数据格式转换为另一个应用程序的数据格式。我们称此步骤为翻译。
Now that we can send messages across channels, we can establish a very basic form of integration. However, we promised that simple integration is an oxymoron, so let's see what is missing. We mentioned that integration solutions often have limited control over the applications they are integrating, such as the internal data formats used by the applications. For example, one data format may store the customer name in two fields, called FIRST_NAME and LAST_NAME, while the other system may use a single field called Customer_Name. Likewise, one system may support multiple customer addresses, while the other system supports only a single address. Because the internal data format of an application can often not be changed, the middleware needs to provide some mechanism to convert one application's data format in the other's format. We call this step translation.
到目前为止,我们可以将数据从一个系统发送到另一个系统,并适应数据格式的差异。如果我们集成两个以上的系统会发生什么?数据必须移动到哪里?我们可以期望每个应用程序为其通过通道发送的数据指定目标系统。例如,如果客户服务系统中的客户地址发生变化,我们可以让该系统负责将数据发送到存储客户地址副本的所有其他系统。随着系统数量的增加,这变得非常乏味,并且需要发送系统了解所有其他系统。每次添加新系统时,客户服务系统都必须调整以适应新环境。如果中间件可以负责将消息发送到正确的位置,事情就会容易得多。这是一个角色路由组件,例如消息代理。
So far, we can send data from one system to another and accommodate differences in data formats. What happens if we integrate more than two systems? Where does the data have to be moved? We could expect each application to specify the target system(s) for the data it is sending over the channel. For example, if the customer address changes in the customer care system, we could make that system responsible for sending the data to all other systems that store copies of the customer address. As the number of systems increases, this becomes very tedious and requires the sending system to have knowledge about all other systems. Every time a new system is added, the customer care system would have to be adjusted to the new environment. Things would be a lot easier if the middleware could take care of sending messages to the correct places. This is the role of a routing component, such as a message broker.
集成解决方案很快就会变得复杂,因为它们处理多种应用程序、数据格式、通道、路由和转换。所有这些元素可能分布在多个操作平台和地理位置。为了了解系统内部发生了什么,我们需要系统管理功能。该子系统监视数据流,确保所有应用程序和组件都启动并运行,并向中央位置报告错误情况。
Integration solutions can quickly become complex because they deal with multiple applications, data formats, channels, routing, and transformation. All these elements may be spread across multiple operating platforms and geographic locations. In order to have any idea what is going on inside the system, we need a systems management function. This subsystem monitors the flow of data, makes sure that all applications and components are up and running, and reports error conditions to a central location.
我们的集成解决方案现在已基本完成。我们可以将数据从一个系统转移到另一个系统,适应数据格式的差异,将数据路由到所需的系统,并监控解决方案的性能。到目前为止,我们假设应用程序将数据作为消息发送到通道。然而,大多数打包应用程序和遗留应用程序以及许多自定义应用程序都没有准备好参与集成解决方案。我们需要一个消息端点来将系统显式连接到集成解决方案。端点可以是一段特殊的代码,也可以是集成软件供应商提供的通道适配器。
Our integration solution is now almost complete. We can move data from one system from another, accommodate differences in the data format, route the data to the required systems, and monitor the performance of the solution. So far, we assumed that an application sends data as a message to the channel. However, most packaged and legacy applications and many custom applications are not prepared to participate in an integration solution. We need a message endpoint to connect the system explicitly to the integration solution. The endpoint can be a special piece of code or a Channel Adapter provided by an integration software vendor.
了解基于消息的集成解决方案的最佳方法是通过一个具体示例。让我们考虑一下 Widgets & Gadgets 'R Us (WGRUS),这是一家在线零售商,从制造商那里购买小部件和小工具并将其转售给客户。
The best way to understand message-based integration solutions is by walking through a concrete example. Let's consider Widgets & Gadgets 'R Us (WGRUS), an online retailer that buys widgets and gadgets from manufacturers and resells them to customers.
WGRUS 生态系统
WGRUS Ecosystem
对于此示例,我们假设解决方案需要支持以下要求。当然,为了简洁起见,我们对需求进行了一些简化,但尽管如此,此类需求在实际业务中还是经常出现。
For this example, we assume that the solution needs to support the following requirements. Naturally, we simplified the requirements a bit for sake of brevity, but nevertheless these types of requirements occur frequently in real businesses.
接受订单: 客户可以通过网络、电话或传真下订单。
Take Orders: Customers can place orders via Web, phone, or fax.
处理订单: 处理订单涉及多个步骤,包括验证库存、运送货物以及向客户开具发票。
Process Orders: Processing an order involves multiple steps, including verifying inventory, shipping the goods, and invoicing the customer.
查看状态: 客户可以查看订单状态。
Check Status: Customers can check the order status.
更改地址: 客户可以使用 Web 前端更改其帐单和送货地址。
Change Address: Customers can use a Web front-end to change their billing and shipping address.
新目录: 供应商定期更新其目录。WGRUS 需要根据新目录更新其定价和供货情况。
New Catalog: The suppliers update their catalog periodically. WGRUS needs to update its pricing and availability based on the new catalogs.
公告: 客户可以订阅 WGRUS 的选择性公告。
Announcements: Customers can subscribe to selective announcements from WGRUS.
测试和监控: 操作人员需要能够监控所有单独的组件以及它们之间的消息流。
Testing and Monitoring: The operations staff needs to be able to monitor all individual components and the message flow between them.
我们分别解决每个需求,并使用本书中介绍的模式语言描述解决方案的替代方案和权衡。我们将从简单的消息流架构开始,并随着我们解决日益复杂的需求而引入更复杂的概念,例如流程管理器。
We tackle each of these requirements separately and describe the solution alternatives and trade-offs using the pattern language introduced in this book. We will start with a simple message flow architecture and introduce more complex concepts, such as a Process Manager, as we address increasingly complex requirements.
与大多数集成场景一样,WGRUS 不是所谓的“绿地”实施,而是由各种打包和自定义应用程序组成的现有 IT 基础设施的集成。事实上,我们必须使用现有的应用程序,这常常使集成工作充满挑战。在我们的示例中,WGRUS 运行以下系统(见图)。
As in most integration scenarios, WGRUS is not a so-called "green field" implementation, but rather the integration of an existing IT infrastructure comprised of a variety of packaged and custom applications. The fact that we have to work with existing applications often makes integration work challenging. In our example, WGRUS runs the following systems (see figure).
WGRUS IT 基础设施
WGRUS IT Infrastructure
WGRUS 有四种不同的渠道与客户互动。客户可以访问公司网站、致电呼叫中心的客户服务代表或通过传真提交订单。客户还可以通过电子邮件接收通知。
WGRUS has four different channels to interact with customers. Customers can visit the company Web site, call the customer service representative at the call center, or submit orders via fax. Customers can also receive notifications via e-mail.
WGRUS 的内部系统由会计系统(还包括计费功能)和运输系统(计算运输费用并与运输公司交互)组成。由于历史原因,WGRUS 有两个库存和目录系统。WGRUS 过去只销售小部件,但后来收购了另一家销售小部件的零售商。
WGRUS's internal systems are comprised of the accounting system, which also includes billing functions, and the shipping system that computes shipping charges and interacts with the shipping companies. For historic reasons, WGRUS has two inventory and catalog systems. WGRUS used to sell only widgets but acquired another retailer that sells gadgets.
我们要实现的第一个功能是接受订单。接受订单是一件好事,因为订单带来收入。然而,目前下订单是一个繁琐的手工过程,因此每个订单产生的成本很高。
The first function we want to implement is taking orders. Taking orders is a good thing because orders bring revenue. However, placing orders is currently a tedious manual process, so the cost incurred with each order is high.
简化订单处理的第一步是统一接单。客户可以通过以下三种渠道之一下订单:网站、呼叫中心或传真。不幸的是,每个系统都基于不同的技术,并以不同的数据格式存储传入的订单。呼叫中心系统是一个打包的应用程序,而网站是一个定制的J2EE应用程序。入站传真系统需要将数据手动输入到小型 Microsoft Access 应用程序中。我们希望平等对待所有订单,无论其来源如何。例如,客户应该能够通过呼叫中心下订单并在网站上检查订单状态。
The first step to streamlining order processing is to unify taking orders. A customer can place orders over one of three channels: Web site, call center, or fax. Unfortunately, each system is based on a different technology and stores incoming orders in a different data format. The call center system is a packaged application, while the Web site is a custom J2EE application. The inbound fax system requires manual data entry into a small Microsoft Access application. We want to treat all orders equally, regardless of their source. For example, a customer should be able to place an order via the call center and check the order status on the Web site.
由于下订单是一个连接多个系统的异步过程,因此我们决定实施面向消息的中间件解决方案来简化订单输入流程。打包的呼叫中心应用程序在开发时并未考虑集成,因此我们必须使用通道适配器将其连接到消息传递系统。通道适配器是一个组件,可以附加到应用程序并在应用程序内部发生事件时将消息发布到消息通道。使用一些通道适配器,应用程序甚至可能不知道适配器的存在。例如,数据库适配器可以向特定表添加触发器,以便应用程序每次插入一行数据时,都会向消息通道发送一条消息。 通道适配器还可以以相反的方向工作,从消息通道消费消息并触发应用程序内的操作作为响应。
Because placing an order is an asynchronous process that connects many systems, we decide to implement a message-oriented middleware solution to streamline the order entry process. The packaged call center application was not developed with integration in mind so that we have to connect it to the messaging system using a Channel Adapter. A Channel Adapter is a component that can attach to an application and publish messages to a Message Channel whenever an event occurs inside the application. With some Channel Adapters, the application may not even be aware of the presence of the adapter. For example, a database adapter may add triggers to specific tables so that every time the application inserts a row of data, a message is sent to the Message Channel. Channel Adapters can also work in the opposite direction, consuming messages off a Message Channel and triggering an action inside the application in response.
我们对入站传真应用程序使用相同的方法,将通道适配器连接到应用程序数据库。由于 Web 应用程序是自定义构建的,因此我们在应用程序内部实现消息端点代码。我们使用消息传递网关将应用程序代码与消息传递特定的代码隔离。
We use the same approach for the inbound fax application, connecting the Channel Adapter to the application database. Because the Web application is custom built, we implement the Message Endpoint code inside the application. We use a Messaging Gateway to isolate the application code from the messaging-specific code.
从三个不同渠道接受订单
Taking Orders from Three Different Channels
由于每个系统对传入订单使用不同的数据格式,因此我们使用三个消息转换器将不同的数据格式转换为遵循规范数据模型的通用新订单消息。规范数据模型定义了独立于任何特定应用程序的消息格式,以便所有应用程序都可以采用这种通用格式相互通信。如果应用程序的内部格式发生变化,则只有受影响的应用程序和公共消息通道之间的消息转换器必须更改,而所有其他应用程序和消息转换器保持不受影响。用一个规范数据模型意味着我们处理两种类型的消息:规范(公共)消息和特定于应用程序的(私有)消息。应用程序特定的消息不应由该应用程序和关联的消息转换器以外的任何组件使用。 为了强化这一策略,我们以应用程序开头来命名特定于应用程序的消息通道:例如WEB_NEW_ORDER 。相反,携带规范消息的通道以消息的意图命名,没有任何前缀:例如NEW_ORDER。
Because each system uses a different data format for the incoming orders, we use three Message Translators to convert the different data formats into a common New Order message that follows a Canonical Data Model. A Canonical Data Model defines message formats that are independent from any specific application so that all applications can communicate with each other in this common format. If the internal format of an application changes, only the Message Translator between the affected application and the common Message Channel has to change, while all other applications and Message Translators remain unaffected. Using a Canonical Data Model means that we deal with two types of messages: canonical (public) messages and application-specific (private) messages. Application-specific messages should not be consumed by any component other than that application and the associated Message Translator. To reinforce this policy, we name application-specific Message Channels starting with the name of the application: for example, WEB_NEW_ORDER. In contrast, channels carrying canonical messages are named after the intent of the message without any prefix: for example, NEW_ORDER.
我们通过点对点通道将每个通道适配器连接到消息转换器,因为我们希望确保每个订单消息仅被消耗一次。如果我们将转换逻辑编程到消息传递网关中,我们就可以在不使用 Web 界面消息转换器的情况下完成任务。 然而,手动编码转换函数可能很乏味且容易出错,我们更喜欢使用一致的方法。附加的消息转换器还允许我们保护新订单流免受 Web 界面数据格式的微小变化的影响。所有消息翻译器发布到相同的NEW_ORDER 点对点通道,以便可以通过该通道处理订单,而无需考虑订单的来源。
We connect each Channel Adapter to the Message Translator via a Point-to-Point Channel because we want to be sure that each order message is consumed only once. We could get away without using a Message Translator for the Web interface if we programmed the transformation logic into the Messaging Gateway. However, hand-coding transformation functions can be tedious and error-prone, and we prefer to use a consistent approach. The additional Message Translator also allows us to shield the New Order flow from minor changes in the Web interface data format. All Message Translators publish to the same NEW_ORDER Point-to-Point Channel so that orders can be processed off this channel without regard to the order's origin.
NEW_ORDER消息通道是所谓的数据类型通道,因为它仅承载一种 类型的消息:新订单。这使得消息消费者很容易知道期望的消息类型。新订单消息本身被设计为文档消息。该消息的目的不是指示接收者采取特定操作,而是将文档传递给任何感兴趣的接收者,后者可以自由决定如何处理该文档。
The NEW_ORDER Message Channel is a so-called Datatype Channel because it carries messages of only one type: new orders. This makes it easy for message consumers to know what type of message to expect. The New Order message itself is designed as a Document Message. The intent of the message is not to instruct the receiver to take a specific action, but rather to pass a document to any interested recipient, who is free to decide how to process the document.
现在我们有了独立于消息源的一致订单消息,我们准备好处理订单了。为了履行订单,我们需要完成以下步骤:
Now that we have a consistent order message that is independent from the message source, we are ready to process orders. To fulfill an order, we need to complete the following steps:
验证客户的信用状况。 如果客户有未付账单,我们要拒绝新订单。
Verify the customer's credit standing. If the customer has outstanding bills, we want to reject the new order.
核实库存。 我们无法履行没有库存的商品订单。
Verify inventory. We can't fulfill orders for items that are not in stock.
如果客户信誉良好并且我们有库存,我们希望发货并向客户开具账单。
If the customer is in good standing and we have inventory, we want to ship the goods and bill the customer.
我们可以使用统一建模语言 (UML) 活动图来表达这一系列事件。活动图具有相对简单的语义,是描述包含并行活动的流程的好工具。符号非常简单;连续的活动通过简单的箭头连接起来。并行活动通过代表分叉和连接操作的粗黑条连接。分叉操作会导致所有连接的活动同时启动,而连接操作仅在所有传入活动完成后才继续。
We can express this sequence of events using a Unified Modeling Language (UML) activity diagram. Activity diagrams have relatively simple semantics and are a good tool to depict processes that include parallel activities. The notation is very simple; sequential activities are connected by simple arrows. Parallel activities are connected by a thick black bar representing fork and join actions. A fork action causes all connected activities to start simultaneously, while the join action continues only after all incoming activities have been completed.
我们的活动图(见图)旨在并行执行“检查库存”任务和“验证客户信誉”任务。连接栏会等到两个活动都完成后才允许下一个活动开始。下一个活动验证这两个步骤的结果:我们是否有库存,客户信誉良好吗?如果两个条件都满足,则流程将继续履行订单。否则,我们将过渡到异常处理活动。例如,我们可能会打电话提醒客户支付最后一张发票或发送电子邮件让他或她知道订单将被延迟。因为本书重点关注面向消息的集成的设计方面而不是工作流建模,我们暂时将异常处理过程的细节放在一边。有关工作流架构和工作流建模的非常好的讨论,请参阅[雷曼]和[夏普]。
Our activity diagram (see figure) is designed to execute the Check Inventory task and the Validate Customer Standing task in parallel. The join bar waits until both activities are completed before it allows the next activity to start. The next activity verifies the results of both steps: Do we have inventory, and is the customer in good standing? If both conditions are fulfilled, the process goes on to fulfill the order. Otherwise, we transition to an exception-handling activity. For example, we may call to remind the customer to pay the last invoice or send an e-mail letting him or her know that the order will be delayed. Because this book focuses on the design aspects of message-oriented integration rather than on workflow modeling, we leave the details of the exception-handling process aside for now. For a very good discussions of workflow architecture and workflow modeling, refer to [Leyman] and [Sharp].
订单处理活动图
Activity Diagram for Order Processing
事实证明,活动图中的活动相对较好地映射到 WGRUS IT 部门的系统。会计系统验证客户的信用状况,库存系统检查库存,运输系统启动货物的实际运输。会计系统还充当计费系统并发送发票。我们可以看到,订单的处理是一个典型的分布式业务流程的实现。
It turns out that the activities in the activity diagram map relatively nicely to the systems in WGRUS's IT department. The accounting system verifies the customer's credit standing, the inventory systems check the inventory, and the shipping system initiates the physical shipping of goods. The accounting system also acts as the billing system and sends invoices. We can see that the processing of orders is a typical implementation of a distributed business process.
为了将逻辑活动图转换为集成设计,我们可以使用发布-订阅通道来实现 fork 操作,并使用聚合器来实现 join 操作。发布-订阅通道向所有活跃消费者发送消息;聚合器接收多个传入消息并将它们组合成单个传出消息(见图)。
To convert the logical activity diagram into an integration design, we can use a Publish-Subscribe Channel to implement the fork action and an Aggregator to implement the join action. A Publish-Subscribe Channel sends a message to all active consumers; an Aggregator receives multiple incoming messages and combines them into a single, outgoing message (see figure).
使用异步消息传递的订单处理实现
Order Processing Implementation Using Asynchronous Messaging
在我们的示例中,发布-订阅通道将新订单消息发送到会计系统和库存系统。聚合器组合来自两个系统的结果消息并将组合的消息传递到基于内容的路由器。基于内容的路由器是一个组件,它使用消息并将其未经修改地发布到基于路由器内部编码的规则选择的其他通道。基于内容的路由器相当于UML活动图中的分支。在这种情况下,如果库存检查和信用检查都是肯定的,则基于内容的路由器将消息转发到VALIDATED_ORDER频道。该通道是发布-订阅通道,因此经过验证的订单会同时到达运输和计费系统。如果客户信誉不佳或者我们手头没有库存,基于内容的路由器会将消息转发到INVALID_ORDER通道。异常处理流程(图中未显示)侦听此通道上的消息并通知客户订单被拒绝。
In our example, the Publish-Subscribe Channel sends the New Order message to both the accounting system and the inventory system. The Aggregator combines the result messages from both systems and passes the combined message to a Content-Based Router. A Content-Based Router is a component that consumes a message and publishes it, unmodified, to a choice of other channels based on rules coded inside the router. The Content-Based Router is equivalent to the branch in a UML activity diagram. In this case, if both the inventory check and the credit check have been affirmative, the Content-Based Router forwards the message to the VALIDATED_ORDER channel. This channel is a Publish-Subscribe Channel, so the validated order reaches both the shipping and the billing systems. If the customer is not in good standing or we have no inventory on hand, Content-Based Router forwards the message to the INVALID_ORDER channel. An exception-handling process (not shown in the figure) listens to messages on this channel and notifies the customer of the rejected order.
现在我们已经建立了整体的消息流,我们需要仔细研究一下库存函数。正如我们在需求部分中了解到的,WGRUS 有两种库存系统:一种用于小部件,一种用于小工具。因此,我们必须将库存请求路由到正确的系统。因为我们希望对其他系统隐藏库存系统的特殊性,所以我们插入另一个基于内容的路由器,该路由器根据订购的商品类型将消息路由到正确的库存系统(见图)。例如,所有商品编号以 W 开头的传入消息都会路由到小部件库存系统,所有商品编号以 G 开头的订单都会路由到小工具库存系统。
Now that we have established the overall message flow, we need to have a closer look at the inventory function. As we learned in the requirements section, WGRUS has two inventory systems: one for widgets and one for gadgets. As a result, we have to route the request for inventory to the correct system. Because we want to hide the peculiarities of the inventory systems from the other systems, we insert another Content-Based Router that routes the message to the correct inventory system based on the type of item ordered (see figure). For example, all incoming messages with an item number starting with W are routed to the widget inventory system, and all orders with an item number starting with G are routed to the gadget inventory system.
路由库存请求
Routing the Inventory Request
请注意,基于内容的路由器和库存系统之间的点对点通道上的消息意图与之前的通道不同。这些通道包含命令消息,指示系统执行指定命令的消息,在本例中验证项目的库存。
Note that the intent of messages on the Point-to-Point Channels between the Content-Based Router and the inventory systems is different from the previous channel. These channels contain Command Messages, messages that instruct the system to execute the specified command, in this case verifying the inventory of an item.
由于小部件库存系统和小工具库存系统使用不同的内部数据格式,因此我们再次插入消息转换器以将规范的新订单消息格式转换为系统特定的格式。
Because the widget inventory system and the gadget inventory system use different internal data formats, we again insert Message Translators to convert from the canonical New Order message format to a system-specific format.
如果订单商品既不以 W 也不以 G 开头,会发生什么情况?基于内容的路由器将消息路由到INVALID_ORDER通道,以便可以通过通知客户等方式相应地处理无效订单。该通道是无效消息通道的典型示例。 它强调了这样一个事实:消息的含义会根据其所在的渠道而变化。NEW_ORDER通道和INVALID_ORDER通道都传输相同类型的消息,但在一种情况下正在处理新订单,而在另一种情况下订单被视为无效。
What happens if the order item starts neither with W nor G? The Content-Based Router routes the message to the INVALID_ORDER channel so that the invalid order can be processed accordingly, by notifying the customer, for example. This channel is a typical example of an Invalid Message Channel. It highlights the fact that the meaning of a message changes depending on what channel it is on. Both the NEW_ORDER channel and the INVALID_ORDER channel transport the same type of message, but in one case a new order is being processed, while in the other case the order is deemed invalid.
到目前为止,我们假设每个订单只包含一个商品。这对我们的客户来说非常不方便,因为他们必须为每件商品下新订单。此外,我们最终会向同一客户运送多个订单,并产生不必要的运输费用。但是,如果我们允许一个订单中包含多个商品,那么哪个库存系统应该验证该订单的库存?我们可以使用发布-订阅通道将订单发送到每个库存系统,以挑选它可以处理的项目。但是无效的物品会怎样呢?我们如何注意到库存系统都没有处理该物品?我们想要维护基于内容的路由器的中央控制给了我们,但我们需要能够单独路由每个订单项目。
So far, we have assumed that each order contains only a single item. This would be pretty inconvenient for our customers because they would have to place a new order for each item. Also, we would end up shipping multiple orders to the same customer and incur unnecessary shipping costs. However, if we allow multiple items inside an order, which inventory system should verify the inventory for this order? We could use a Publish-Subscribe Channel to send the order to each inventory system to pick out the items that it can process. But what would then happen to invalid items? How would we notice that neither inventory system processed the item? We want to maintain the central control the Content-Based Router gives us, but we need to be able to route each order item individually.
因此,我们插入一个Splitter ,该组件将单个消息分成多个单独的消息。在我们的例子中,拆分器将单个订单消息拆分为多个订单项目消息。然后,可以像以前一样使用基于内容的路由器将每个订单项目消息路由到正确的库存系统;见下图。
Therefore, we insert a Splitter, a component that breaks a single message into multiple individual messages. In our case, the Splitter splits a single Order message into multiple Order Item messages. Each Order Item message can then be routed to the correct inventory system using a Content-Based Router as before; see the following figure.
单独处理订单项目
Processing Order Items Individually
当然,当所有物品的库存都得到验证后,我们需要将消息重新组合成一条消息。我们已经了解到,可以将多个消息组合成单个消息的组件是聚合器。使用 Splitter和Aggregator ,我们可以在逻辑上将单个订单项的消息流与完整订单的消息流分开。
Naturally, when the inventory for all items has been verified, we need to recombine the messages into a single message. We already learned that the component that can combine multiple messages into a single message is the Aggregator. Using both a Splitter and an Aggregator, we can logically separate the message flow for individual order items from the flow for a complete order.
在设计聚合器时,我们必须做出三个关键决定:
When designing an Aggregator, we have to make three key decisions:
哪些消息属于同一组(相关性)?
Which messages belong together (correlation)?
我们如何确定所有消息都已收到(完整性条件)?
How do we determine that all messages are received (the completeness condition)?
我们如何将各个消息组合成一个结果消息(聚合算法)?
How do we combine the individual messages into one result message (the aggregation algorithm)?
让我们一一解决这些问题。我们无法通过客户 ID 关联订单项目,因为客户可能会在短时间内连续下多个订单。因此,我们需要为每个订单分配一个唯一的订单ID。我们通过将内容丰富器插入到接受订单的解决方案部分来实现此目的(见图)。内容丰富器是一个将缺失的数据项添加到传入消息中的组件。在我们的例子中,内容丰富器向消息添加了唯一的订单 ID。
Let's tackle these issues one by one. We can't correlate order items by the customer ID, because a customer may place multiple orders in short succession. Therefore, we need a unique order ID for each order. We accomplish this by inserting a Content Enricher into the part of the solution that takes orders (see figure). A Content Enricher is a component that adds missing data items to an incoming message. In our case, the Content Enricher adds a unique order ID to the message.
通过 Enricher 接受订单
Taking Orders with Enricher
现在我们有了一个订单 ID 来关联订单项消息,我们需要定义订单项聚合器的完整性条件和聚合算法。因为我们将所有消息(包括无效商品)路由到聚合器,所以聚合器可以简单地使用订单中的商品数量(订单消息中的字段之一)进行计数,直到所有订单商品到达为止。聚合算法同样简单。聚合器将所有订单项消息连接回单个订单消息并将其发布到VALIDATED_ORDER 通道。
Now that we have an order ID to correlate Order Item messages, we need to define the completeness condition and the aggregation algorithm for the Order Item Aggregator. Because we route all messages, including invalid items, to the Aggregator, the Aggregator can simply use the number of items in the order (one of the fields in the Order message) to count until all order items arrive. The aggregation algorithm is similarly simple. The Aggregator concatenates all Order Item messages back into a single Order message and publishes it to the VALIDATED_ORDER channel.
Splitter、Message Router和Aggregator的组合相当常见。我们将其称为组合消息处理器。为了简化该图,我们将组合消息处理器的符号插入到原始消息流图中:
The combination of a Splitter, a Message Router, and an Aggregator is fairly common. We refer to it as a Composed Message Processor. To simplify the figure, we insert the symbol for a Composed Message Processor into the original message flow diagram:
修订后的订单流程实施
Revised Order Process Implementation
尽管通过消息通道连接系统,但履行订单可能需要一些时间。例如,我们可能缺少某种商品,并且库存系统可能会保留库存检查消息,直到新商品到达为止。这是异步消息传递的优点之一:通信被设计为按照组件的速度进行。当库存系统保留该消息时,会计系统仍然可以验证客户的信用状况。完成这两个步骤后,聚合器将发布“已验证订单”消息以启动发货和开具发票。
Despite connecting the systems via Message Channels, fulfilling an order can take some amount of time. For example, we may be out of a certain item and the inventory system may be holding the Inventory Check message until new items arrive. This is one of the advantages of asynchronous messaging: the communication is designed to happen at the pace of the components. While the inventory system is holding the message, the accounting system can still verify the customer's credit standing. Once both steps are completed, the Aggregator publishes the Validated Order message to initiate shipment and invoicing.
长期运行的业务流程还意味着客户和经理都可能想知道特定订单的状态。例如,如果某些商品缺货,客户可能决定仅处理有库存的商品。或者,如果客户还没有收到货物,我们可以告诉他或她货物正在运送途中(包括运输公司的追踪号码)或仓库内部出现延误,这会很有用。
A long-running business process also means that both customers and managers are likely to want to know the status of a specific order. For example, if certain items are out of inventory, the customer may decide to process just those items that are in stock. Or, if the customer has not received the goods, it is useful if we can tell him or her that the goods are on their way (including the shipping company's tracking number) or that there is an internal delay in the warehouse.
使用当前设计跟踪订单状态并不那么容易。相关消息流经多个系统。为了确定步骤序列中订单的状态,我们必须知道与该订单相关的“最后”消息。发布-订阅通道的优点之一是我们可以在不干扰消息流的情况下添加其他订阅者。我们可以使用此属性来侦听新的和经过验证的订单并将它们存储在Message Store中。然后,我们可以查询消息存储数据库以获取订单状态(见图):
Tracking the status of an order with the current design is not so easy. Related messages flow through multiple systems. To ascertain the status of the order in the sequence of steps, we would have to know the "last" message related to this order. One of the advantages of a Publish-Subscribe Channel is that we can add additional subscribers without disturbing the flow of messages. We can use this property to listen in to new and validated orders and store them in a Message Store. We could then query the Message Store database for the status of an order (see figure):
添加消息存储以跟踪订单状态
Adding a Message Store to Track Order Status
在我们使用点对点通道的情况下,我们不能简单地将订阅者添加到通道中,因为点对点通道确保每条消息仅由单个订阅者消费。但是,我们可以插入一个Wire Tap,这是一个简单的组件,它使用一个通道的消息并将其发布到两个通道。然后我们可以使用第二个通道将消息发送到消息存储;见图。
In situations where we use a Point-Point Channel, we cannot simply add a subscriber to the channel, because a Point-to-Point Channel ensures that each message is only consumed by a single subscriber. However, we can insert a Wire Tap, a simple component that consumes a message off one channel and publishes it to two channels. We can then use the second channel to send messages to the Message Store; see figure.
使用窃听器跟踪消息
Tracking Messages with a Wire Tap
将消息数据存储在中央数据库中还有另一个显着的优点。在最初的设计中,每条消息都必须携带无关的数据,以便继续处理该消息。例如,验证客户信誉消息可能必须传输各种客户数据,即使它只需要客户 ID。此附加数据是必要的,以便生成的消息仍然包含原始订单消息中的所有数据。在消息流开始时将 New Order 消息存储在Message Store中的优点是所有后续组件都可以引用Message Store用于重要的消息数据,而无需所有中间步骤携带数据。我们将这样的功能称为“索赔检查消息”,可以“签入”数据以供以后检索。这种方法的缺点是访问中央数据存储不如通过异步消息通道发送消息可靠。
Storing message data in a central database has another significant advantage. In the original design, each message had to carry extraneous data in order to continue processing the message down the line. For example, the Validate Customer Standing message may have had to transport all sorts of customer data even though it required only the customer ID. This additional data is necessary so that the resulting message still contains all data from the original order message. Storing the New Order message in a Message Store at the beginning of the message flow has the advantage that all subsequent components can refer to the Message Store for important message data without all intermediate steps having to carry the data along. We refer to such a function as Claim Check messages can "check in" data for later retrieval. The downside of this approach is that accessing a central data store is not as reliable as sending messages across asynchronous Message Channels.
现在,消息存储负责维护与新消息相关的数据以及消息在流程中的进度。这些数据为我们提供了足够的信息,可以使用消息存储来确定流程中接下来所需的步骤,而不是使用固定的消息通道连接组件。例如,如果数据库包含来自库存系统和计费系统的回复消息,我们可以得出结论,订单已经过验证,并且可以向运输和计费系统发送消息。我们可以在消息存储中直接完成此决定,而不是在单独的聚合器组件中做出此决定。实际上,我们正在将消息存储转变为流程管理器。
Now, the Message Store is responsible for maintaining data related to the new message as well as the progress of the message within the process. This data gives us enough information to use the Message Store to determine the next required steps in the process rather than connecting components with fixed Message Channels. For example, if the database contains reply messages from both the inventory systems and the billing system, we can conclude that the order has been validated and can send a message to the shipping and billing systems. Instead of making this decision in a separate Aggregator component, we can do it right in the Message Store. Effectively, we are turning the Message Store into a Process Manager.
流程管理器是管理系统中消息流的核心组件。流程管理器提供两个主要功能:
A Process Manager is a central component that manages the flow of messages through the system. The Process Manager provides two main functions:
在消息之间存储数据(在“流程实例”内)
Storing data between messages (inside a "process instance")
跟踪进度并确定下一步(通过使用“流程模板”)
Keeping track of progress and determining the next step (by using a "process template")
使用流程管理器处理订单
Processing Orders with a Process Manager
这种架构将各个系统(例如,库存系统)转变为共享业务功能,其他组件可以将其作为服务进行访问,从而提高重用性并简化维护。这些服务可以通过消息流连接在一起(例如,使用组合消息处理器检查每个订单项的库存状态)或通过流程。使用流程管理器使得更改消息流比我们以前的方法容易得多。
This architecture turns the individual systems (e.g., the inventory systems) into shared business functions that can be accessed by other components as services, thus increasing reuse and simplifying maintenance. The services can be wired together via a message flow (for example, using a Composed Message Processor to check inventory status for each order item) or orchestrated via a Process Manager. Using a Process Manager makes changing the flow of messages much easier than our previous approach.
新的架构将所有服务公开给公共服务总线,以便可以从任何其他组件调用它们。我们可以通过添加从中央服务注册表查找(“发现”)服务的设施,将 WGRUS IT 基础架构转变为 SOA。为了参与此 SOA,每个服务都必须提供附加功能。例如,每个服务都必须公开一个描述该服务提供的功能的接口契约。每个请求-答复服务还需要支持返回地址的概念。 退货地址允许调用者(服务使用者)指定服务应发送回复消息的通道。这对于允许在不同上下文中重用服务非常重要,每个上下文可能需要自己的回复消息通道。
The new architecture exposes all services to a common services bus so that they can be invoked from any other component. We could turn the WGRUS IT infrastructure into an SOA by adding facilities to look up ("discover") a service from a central service registry. In order to participate in this SOA, each service would have to provide additional functions. For example, each service would have to expose an interface contract that describes the functions provided by the service. Each request-reply service also needs to support the concept of a Return Address. A Return Address allows the caller (the service consumer) to specify the channel where the service should send the reply message. This is important to allow the service to be reused in different contexts, each of which may require its own channel for reply messages.
流程管理器本身使用持久存储(通常是文件或关系数据库)来存储与每个流程实例关联的数据。为了允许 Web 界面查询订单的状态,我们可以向流程管理器或订单数据库发送一条消息。然而,检查状态是一个同步过程,客户希望立即得到响应。由于Web界面是自定义应用程序,因此我们决定直接访问订单数据库来查询订单状态。这种形式的共享数据库是最简单、最有效的方法,并且我们始终确保Web界面显示最新的状态。这种方法的潜在缺点是 Web 界面与数据库紧密耦合,这是我们愿意采取的权衡。
The Process Manager itself uses a persistent store (typically files or a relational database) to store data associated with each process instance. To allow the Web interface to query the status of an order, we could send a message to the Process Manager or the order database. However, checking status is a synchronous processthe customer expects the response right away. Because the Web interface is a custom application, we decide to access the order database directly to query the order status. This form of Shared Database is the simplest and most efficient approach, and we are always ensured that the Web interface displays the most current status. The potential downside of this approach is that the Web interface is tightly coupled to the database, a trade-off that we are willing to take.
将系统公开为服务的一个困难是由于许多遗留系统在构建时没有考虑到返回地址等功能。因此,我们用智能代理“包装”对遗留系统的访问。该智能代理通过附加功能增强了基本系统服务,使其能够参与 SOA。为此,智能代理拦截来自基本服务的请求和回复消息(见图)。
One difficulty in exposing systems as services results from the fact that many legacy systems were not built with features such as Return Address in mind. Therefore, we "wrap" access to the legacy system with a Smart Proxy. This Smart Proxy enhances the basic system service with additional capability so that it can participate in an SOA. To do this, the Smart Proxy intercepts both request and reply messages to and from the basic service (see figure).
插入智能代理将遗留系统转变为共享服务
Inserting a Smart Proxy to Turn a Legacy System into a Shared Service
智能代理可以存储来自请求消息的信息(例如,请求者指定的返回地址)并使用该信息来处理回复消息(例如,将其路由到正确的回复通道)。智能代理对于跟踪外部服务的服务质量(例如响应时间)也非常有用。
The Smart Proxy can store information from the request message (e.g., the Return Address specified by the requestor) and use this information to process the reply message, (e.g., route it to the correct reply channel). A Smart Proxy is also very useful to track quality of service (e.g., response times) of an external service.
WGRUS 需要处理多个地址。例如,发票必须发送到客户的帐单地址,而货物则运送到送货地址。我们希望允许客户通过Web界面维护所有这些地址,以消除不必要的手动步骤。
WGRUS needs to deal with a number of addresses. For example, the invoice has to be sent to the customer's billing address, while the goods are shipped to the shipping address. We want to allow the customer to maintain all these addresses through the Web interface to eliminate unnecessary manual steps.
我们可以选择两种基本方法来获取正确的账单和送货地址到账单和送货系统:
We can choose between two basic approaches to get the correct billing and shipping addresses to the billing and shipping systems:
每条新订单消息中都包含地址数据
Include address data with every New Order message
在每个系统中存储地址数据并复制更改
Store address data in each system and replicate changes
第一个选项的优点是我们可以使用现有的集成通道来传输附加信息。一个潜在的缺点是流经中间件基础设施的额外数据;即使地址更改的频率可能要低得多,我们也会将地址数据与每个订单一起传递。
The first option has the advantage that we can use an existing integration channel to transport the additional information. A potential downside is the additional data flowing across the middleware infrastructure; we pass the address data along with every order even though the address may change much less frequently.
在实施第一个选项时,我们需要考虑到计费和运输系统是打包的应用程序,并且在设计时可能没有考虑到集成。因此,他们不太可能接受新订单的地址,而是使用存储在本地数据库中的地址。为了使系统能够使用新订单消息更新地址,我们需要在计费系统(和运输系统)中执行两个功能:首先,我们必须更新地址,然后我们必须发送账单(或运送商品)。由于两条消息的顺序很重要,因此我们插入一个简单的流程管理器接收新订单消息的组件(其中包括当前的送货和帐单地址,并向帐单(或送货)系统发布两条单独的消息(见图)。
When implementing the first option, we need to consider that the billing and shipping systems are packaged applications and were likely not designed with integration in mind. As such, they are unlikely to be able to accept addresses with a new order but rather use the address that is stored in their local database. To enable the systems to update the address with the New Order message, we need to execute two functions in the billing system (and the shipping system): First, we must update the address, and then we must send the bill (or ship the goods). Because the order of the two messages matters, we insert a simple Process Manager component that receives a New Order message (which includes the current shipping and billing addresses and publishes two separate messages to the billing (or shipping) system (see figure).
在新订单消息中包含地址数据
Including Address Data in the New Order Message
我们需要记住,通道适配器要求消息采用应用程序使用的专有格式(使用所谓的私有消息)。由于新订单消息以规范消息格式到达,因此我们需要在两种格式之间执行转换。我们可以将转换构建到Process Manager 中,但实际上我们更喜欢外部消息转换器,以便Process不会受到应用程序所需的可能复杂的数据格式的影响。
We need to keep in mind that the Channel Adapters require messages to be formatted in the proprietary formats used by the applications (using so-called private messages). Because the New Order message arrives in the canonical message format, we need to perform a translation between the two formats. We could build the transformation into the Process Manager, but we actually prefer external Message Translators so that the logic inside the Process Manager is not affected by the possibly complicated data format required by the applications.
第二个选项使用数据复制将地址更改传播到所有受影响的系统,独立于新订单流程。每当 Web 界面中的地址信息发生更改时,我们都会使用发布-订阅通道将更改传播到所有感兴趣的系统。 系统在内部存储更新后的地址,并在订单消息到达时使用它。这种方法减少了消息流量(假设客户更改地址的频率低于下订单的频率)。它还可以减少系统之间的耦合。任何使用地址的系统都可以订阅ADDRESS_CHANGE通道,而不会影响任何其他系统。
The second option uses data replication to propagate address changes to all affected systems independently of the New Order process. Whenever the address information changes in the Web interface, we propagate the changes to all interested systems using a Publish-Subscribe Channel. The systems store the updated address internally and use it when an Order message arrives. This approach reduces message traffic (assuming customers change addresses less frequently than they place orders). It can also reduce coupling between systems. Any system that uses an address can subscribe to the ADDRESS_CHANGE channel without affecting any other systems.
由于我们正在处理多种类型的地址(送货地址和帐单地址),因此我们需要确保每个系统中仅存储正确类型的地址。如果地址是帐单地址,我们需要避免向运输系统发送地址更改消息。我们通过使用消息过滤器来实现这一点,该器仅传递符合特定条件的消息(见图)。
Because we are dealing with multiple types of addresses (shipping and billing addresses), we need to make sure that only the right type of address is stored in each system. We need to avoid sending an address change message to the shipping system if the address is a billing address. We accomplish this by using Message Filters that pass only messages matching certain criteria (see figure).
我们还使用消息转换器将通用地址更改消息转换为应用程序使用的特定消息格式。在这种情况下,我们不必为Web 界面使用消息转换器,因为我们将规范数据模型定义为与 Web 界面应用程序的格式相同。如果我们想在未来引入其他更改地址的方式,这可能会限制我们的灵活性,但目前已经足够了。
We also use Message Translators to translate the generic Address Change message into the specific message format used by the applications. In this case, we do not have to use a Message Translator for the Web interface because we define the Canonical Data Model as equal to the format of the Web interface application. This could limit our flexibility if we want to introduce other ways of changing addresses in the future, but for now it is sufficient.
通过单独的发布-订阅通道传播地址更改
Propagating Address Changes via a Separate Publish-Subscribe Channel
运输和计费系统都将地址存储在关系数据库中,因此我们使用数据库通道适配器来更新每个系统中的数据。
Both the shipping and the billing systems store addresses in a relational database, so we use a database Channel Adapter to update the data in each system.
我们如何在这两个选项之间做出决定?在我们的情况下,消息流量并不是什么大问题,因为我们每天只处理几百个订单,因此任何一种解决方案都相当有效。主要的决策驱动因素将是应用程序的内部结构。我们可能无法将地址直接插入数据库,而是通过应用程序的业务层。在这种情况下,应用程序可以执行额外的验证步骤并记录地址更改活动。系统甚至可以编程为每次地址更改时通过电子邮件向客户发送确认消息。如果每个订单都进行更新,这会变得非常烦人。
How do we decide between the two options? In our situation, the message traffic is not much of a concern because we process only a few hundred orders a day, so either solution would be reasonably efficient. The main decision driver is going to be the internal structure of the applications. We may not be able to insert the addresses directly into the database, but rather through the applications' business layer. In this case, the applications may perform additional validation steps and record the address change activity. The system may even be programmed to e-mail a confirmation message to the customer every time the address changes. This would get very annoying if the update occurred with every order. Such a condition would favor propagating address changes using dedicated messages that are sent only when the customer actually changes the address.
一般来说,我们更喜欢定义明确、独立的业务操作,例如“更改地址”和“下订单”,因为它们为我们编排业务流程提供了更大的灵活性。这一切都归结为粒度和相关权衡的问题。由于进行过多的远程调用或发送消息,细粒度的接口可能会导致系统运行缓慢。例如,想象一个接口公开一个单独的方法来更改每个地址字段。如果通信发生在单个应用程序内(仅更新那些已更改的字段),则此方法将非常有效。在集成场景中,发送六到七条消息来更新地址将是一笔巨大的开销,另外,我们还必须处理同步各个消息的问题。细粒度的接口也会导致紧密的耦合。如果我们通过添加新字段来更改地址格式,则必须定义新的消息格式并更改所有其他应用程序以发送附加消息。
In general, we prefer well-defined, self-contained business actions such as "Change Address" and "Place Order" because they give us more flexibility in orchestrating the businesses processes. It all comes down to a question of granularity and the associated trade-offs. Fine-grained interfaces can lead to sluggish systems due to an excessive number of remote calls being made or messages being sent. For example, imagine an interface that exposes a separate method to change each address field. This approach would be efficient if the communication happens inside a single applicationyou update only those fields that changed. In an integration scenario, sending six or seven messages to update an address would be a significant overhead, plus we would have to deal with synchronizing the individual messages. Fine-grained interfaces also lead to tight coupling. If we change the address format by adding a new field, we have to define new message formats and change all other applications to send an additional message.
粗粒度接口可以解决这些问题,但需要付出一定的代价。我们发送的消息较少,因此效率更高且耦合程度较低。然而,过于粗糙的接口会限制我们的灵活性。如果“发送发票”和“更改地址”合并为一个外部函数,那么我们将永远无法在不发送帐单的情况下更改地址。因此,一如既往,最好的答案是折中方案,并且取决于现实生活场景中的具体权衡。
Coarse-grained interfaces solve these issues, but at a cost. We send fewer messages and are therefore more efficient and less tightly coupled. However, interfaces that are too coarse can limit our flexibility. If Send Invoice and Change Address are combined into one external function, we will never be able to change an address without sending a bill. So, as always, the best answer is the happy medium and depends on the specific trade-offs at work in the real-life scenario.
要下订单,客户需要在线查看当前提供的商品及其价格。WGRUS 的目录由各个供应商的产品驱动。然而,WGRUS 向客户提供的服务之一是允许他们在同一网站上查看小部件和小工具,并在一个订单中订购两种类型的商品。此功能是信息门户场景的示例,我们将多个源的信息组合到一个视图中。
To place orders, customers need to see the currently offered items and their prices online. WGRUS's catalog is driven by the offerings from the respective suppliers. However, one of the services that WGRUS provides to its customers is allowing them to view widgets and gadgets on the same site and to order both types of items in a single order. This function is an example of an Information Portal scenariowe combine information from multiple sources into a single view.
事实证明,两家供应商每三个月更新一次产品目录。因此,创建实时消息传递基础设施来将目录更改从供应商传播到 WGRUS 的意义相对较小。相反,我们使用文件传输集成将目录数据从供应商移动到 WGRUS。使用文件的另一个优点是可以使用 FTP 或类似协议在公共网络上轻松高效地传输文件。相比之下,大多数异步消息传递基础设施在公共互联网上无法正常工作。
It turns out that both suppliers update their product catalog once every three months. Therefore, it makes relatively little sense to create a real-time messaging infrastructure to propagate catalog changes from the suppliers to WGRUS. Instead, we use File Transfer integration to move catalog data from suppliers to WGRUS. The other advantage of using files is that they are easily and efficiently transported across public networks using FTP or similar protocols. In comparison, most asynchronous messaging infrastructures do not work well over the public Internet.
我们仍然可以使用转换器和适配器将数据转换为我们的内部目录格式。然而,这些翻译人员一次处理整个目录,而不是一次处理一个项目。如果我们处理相同格式的大量数据,这种方法会更有效。
We still can use translators and adapters to transform the data to our internal catalog format. However, these translators process a whole catalog at once instead of one item at a time. This approach is much more efficient if we are dealing with large amounts of data in the same format.
通过文件传输更新目录数据
Updating Catalog Data via File Transfer
为了改善业务,我们希望每隔一段时间就向客户宣布特价活动。为了避免打扰客户,我们允许每个客户指定他们感兴趣的消息。我们还希望将特定的消息发送给特定的客户子集。例如,我们可能只向优先客户宣布特别优惠。当我们需要向多个收件人发送信息时,我们会立即想到发布-订阅通道。然而,发布-订阅通道有一些缺点。首先,它允许任何订阅者在发布者不知情的情况下收听已发布的消息。例如,我们不希望小客户收到针对大批量客户的特别优惠。发布-订阅通道的第二个缺点是它们只能在本地网络上有效地工作。如果我们通过发布-订阅通道跨广域网发送数据,我们必须向每个收件人发送单独的消息副本。如果收件人对该消息不感兴趣,我们就会产生不必要的网络流量。
In order to improve business, we want to announce specials to our customers every once in a while. To avoid annoying the customers, we allow each customer to specify which messages interest them. We also want to target specific messages to a specific subset of customers. For example, we may announce special deals only to preferred customers. When we need to send information to multiple recipients, a Publish-Subscribe Channel immediately comes to mind. However, a Publish-Subscribe Channel has some disadvantages. First, it allows any subscriber to listen to the published messages without the publisher's knowledge. For example, we would not want smaller customers to receive special offers intended for high-volume customers. The second downside of Publish-Subscribe Channels is that they work efficiently only on local networks. If we send data across wide-area networks via a Publish-Subscribe Channel, we have to send a separate copy of the message to each recipient. If a recipient is not interested in the message, we would have incurred unnecessary network traffic.
因此,我们应该寻找一种解决方案,允许订阅者发布他们的订阅偏好,然后仅向感兴趣(和授权)的客户发送单独的消息。为了执行此功能,我们使用动态收件人列表。动态收件人列表是两种消息路由模式的组合。收件人列表是一个将单个消息传播到一组收件人的路由器。接收者列表和发布-订阅通道之间的主要区别在于,接收者列表专门针对每个收件人,因此可以严格控制谁接收消息。动态路由器是一种其路由算法可以根据控制消息而改变的路由器。这些控制消息可以采用订阅者发出的订阅偏好的形式。动态收件人列表是这两种模式结合的结果
Therefore, we should look for a solution that allows subscribers to issue their subscription preferences and then send individual messages only to interested (and authorized) customers. To perform this function, we use a Dynamic Recipient List. A dynamic Recipient List is the combination of two Message Routing patterns. A Recipient List is a router that propagates a single message to a set of recipients. The main difference between the Recipient List and a Publish-Subscribe Channel is that the Recipient List addresses each recipient specifically and therefore has tight control over who receives messages. A Dynamic Router is a router whose routing algorithm can change based on control messages. These control messages can take the form of subscription preferences issued by the subscribers. A dynamic Recipient List is the result of combining these two patterns
发送带有动态收件人列表的公告
Sending Announcements with a Dynamic Recipient List
如果客户通过电子邮件接收公告,则这些模式的实现可以使用通常由电子邮件系统提供的邮件列表功能。然后,每个收件人渠道都通过电子邮件地址进行标识。同样,如果客户更喜欢通过 Web 服务接口接收公告,则每个接收者通道都由 SOAP 请求实现,并且通道地址是 Web 服务的 URI。此示例说明我们用于描述解决方案设计的模式独立于特定的传输技术。
If customers receive announcements via e-mail, the implementation of these patterns can use the mailing list's features typically supplied by e-mail systems. Each recipient channel is then identified by an e-mail address. Likewise, if customers prefer to receive announcements via a Web services interface, each recipient channel is implemented by a SOAP request, and the channel address is the URI of the Web service. This example illustrates that the patterns we use to describe the solution design are independent of a specific transport technology.
监视消息的正确执行是一项关键的操作和支持功能。消息存储可以为我们提供一些重要的业务指标,例如履行订单的平均时间。但是,我们可能需要更详细的信息才能成功运行集成解决方案。假设我们增强了解决方案以访问外部信用机构,以更好地评估客户的信用状况。即使我们没有显示未付款项,如果客户的信用评级特别差,我们也可能会拒绝客户的订单。这对于没有我们付款历史的新客户特别有用。由于该服务是由外部提供商提供的,因此我们需要为其使用付费。为了验证提供商的发票,我们希望跟踪我们的实际使用情况并核对两个报告。我们不能简单地看订单数量,因为业务逻辑可能不会要求对长期客户进行外部信用检查。此外,我们可能与外部提供商签订了服务质量 (QoS) 协议。例如,如果响应时间超过指定时间,我们可能无需为请求付费。
Monitoring the correct execution of messages is a critical operations and support function. The Message Store can provide us with some important business metrics, such as the average time to fulfill an order. However, we may need more detailed information for the successful operation of an integration solution. Let's assume we enhance our solution to access an external credit agency to better assess our customer's credit standing. Even if we show no outstanding payments, we may want to decline a customer's order if the customer's credit ranking is particularly poor. This is especially useful for new customers who do not have a payment history with us. Because the service is provided by an outside provider, we are charged for its use. To verify the provider's invoice, we want to track our actual usage and reconcile the two reports. We cannot simply go by the number of orders, because the business logic may not request an external credit check for long-standing customers. Also, we may have a quality of service (QoS) agreement with the external provider. For example, if the response time exceeds a specified time, we may not have to pay for the request.
为了确保正确计费,我们希望跟踪我们发出的请求数量以及相关响应到达所需的时间。我们必须能够处理两种具体情况。首先,外部服务一次可以处理多个请求,因此我们需要能够匹配请求和回复消息。其次,由于我们将外部服务视为企业内部的共享服务,因此我们希望允许服务使用者指定一个Return Address ,即服务应发送回复消息的通道。不知道回复发送到哪个通道可能会导致匹配请求和回复消息变得困难。
To make sure we are being billed correctly, we want to track the number of requests we make and the time it takes for the associated response to arrive. We have to be able to deal with two specific situations. First, the external service can process more than one request at a time, so we need to be able to match up request and reply messages. Second, since we treat the external service as a shared service inside our enterprise, we want to allow the service consumer to specify a Return Address, the channel where the service should send the reply message. Not knowing to which channel the reply is being sent can make it difficult to match request and reply messages.
智能代理再次是答案。我们在任何服务使用者和外部服务之间插入智能代理。智能代理拦截对服务的每个请求,并用固定的回复通道替换服务消费者指定的返回地址。这会导致服务将所有回复消息发送到智能代理指定的通道。代理存储原始的返回地址,以便它可以将回复消息转发到消费者最初指定的通道。智能代理还测量来自外部服务的请求和回复消息之间经过的时间。这智能代理将此数据发布到控制总线。控制总线连接到管理控制台,该控制台从许多不同的组件收集指标。
Once again, the Smart Proxy is the answer. We insert the Smart Proxy between any service consumer and the external service. The Smart Proxy intercepts each request to the service and replaces the Return Address specified by the service consumer with a fixed reply channel. This causes the service to send all reply messages to the channel specified by the Smart Proxy. The proxy stores the original Return Address so that it can forward the reply message to the channel originally specified by the consumer. The Smart Proxy also measures the time elapsed between request and reply messages from the external service. The Smart Proxy publishes this data to the Control Bus. The Control Bus connects to a management console that collects metrics from many different components.
插入智能代理来跟踪响应时间
Inserting a Smart Proxy to Track Response Times
除了跟踪我们对外部信用服务的使用情况外,我们还希望确保该服务正常运行。智能代理可以向管理控制台报告在指定超时时间内未收到回复消息的情况。外部服务返回回复消息但消息中的结果不正确的情况更难以检测。例如,如果外部服务出现故障并为每个客户返回零信用评分,我们最终会拒绝每个订单。有两种机制可以帮助我们防止这种情况的发生。首先,我们可以定期将测试消息注入到请求流中。此测试消息请求特定人员的分数以便知道结果。然后,我们可以使用测试数据验证器不仅检查是否收到回复,还检查消息内容的准确性。由于智能代理支持返回地址,测试数据生成器可以指定一个特殊的回复通道来将测试回复与常规回复分开(见图)。
Besides tracking our usage of the external credit service, we also want to make sure that the service is working correctly. The Smart Proxy can report to the management console cases where no reply message is received within a specified time-out period. Much harder to detect are cases where the external service returns a reply message but the results in the message are incorrect. For example, if the external service malfunctions and returns a credit score of zero for every customer, we would end up denying every order. There are two mechanisms that can help us protect against such a scenario. First, we can periodically inject a Test Message into the request stream. This Test Message requests the score for a specific person so that the result is known. We can then use a test data verifier to check not only that a reply was received but also the accuracy of the message content. Because the Smart Proxy supports Return Addresses, the test data generator can specify a special reply channel to separate test replies from regular replies (see figure).
插入测试消息以验证结果是否准确
Inserting Test Messages to Verify Accurate Results
检测故障服务的另一个有效策略是进行统计样本。例如,由于客户信誉不佳,我们预计平均会拒绝不到十分之一的订单。如果我们连续拒绝超过五个订单,这可能表明外部服务或某些业务逻辑出现故障。管理控制台可以通过电子邮件将五个订单发送给管理员,然后管理员可以快速查看数据以验证拒绝是否合理。
Another effective strategy to detect malfunctioning services is to take a statistical sample. For example, we may expect to decline an average of less than one in 10 orders due to customers' poor standing. If we decline more than five orders in a row, this may be an indication that an external service or some business logic is malfunctioning. The management console could e-mail the five orders to an administrator, who can then take a quick look at the data to verify whether the rejections were justified.
我们已经使用不同的集成策略(例如文件传输、共享数据库和异步消息传递)完成了相当广泛的集成场景。我们路由、拆分和聚合消息。我们引入了流程管理器以提供更大的灵活性。我们还添加了监控解决方案正确运行的功能。虽然这个示例的要求确实得到了简化,但我们必须考虑的问题和设计权衡却是非常现实的。解决方案图和描述重点介绍了我们如何使用供应商中立和技术中立的语言来描述每个解决方案,这种语言比高级序列图准确得多。
We have walked through a fairly extensive integration scenario using different integration strategies such as File Transfer, Shared Database, and asynchronous Messaging. We routed, split, and aggregated messages. We introduced a Process Manager to allow for more flexibility. We also added functions to monitor the correct operation of the solution. While the requirements for this example were admittedly simplified, the issues and design trade-offs we had to consider are very real. The solution diagrams and descriptions highlight how we can describe each solution in a vendor-neutral and technology-neutral language that is much more accurate than a high-level sequence diagram.
本章中的集成场景主要关注如何连接现有应用程序。有关如何从自定义应用程序内部发布和使用消息的详细说明,请参阅第 6 章“Interlude:简单消息传递”和第 9 章“Interlude:组合消息传递”中的示例。
The integration scenario in this chapter focuses primarily on how to connect existing applications. For a detailed description of how to publish and consume messages from inside a custom application, see the examples in Chapter 6, "Interlude: Simple Messaging," and Chapter 9, "Interlude: Composed Messaging."
本书的其余部分包含我们在解决方案设计中使用的每种模式的详细描述和代码示例,以及许多相关模式。这些模式按其主要意图分为基本模式、通道模式、消息模式、路由模式、转换模式、端点模式和系统管理模式。这种安排可以轻松地按顺序读取所有模式或查找单个模式作为参考。
The remainder of the book contains detailed descriptions and code examples for each of the patterns that we used in our solution design, as well as many related patterns. The patterns are categorized by their primary intent between base patterns, channel patterns, message patterns, routing patterns, transformation patterns, endpoint patterns, and system management patterns. This arrangement makes it easy to read all patterns in sequence or to look up individual patterns as a reference.
企业集成的任务是使不同的应用程序协同工作以产生一组统一的功能。这些应用程序可以在内部定制开发或从第三方供应商处购买。它们可能在多台计算机上运行,这些计算机可能代表多个平台,并且可能在地理上分散。某些应用程序可能由业务合作伙伴或客户在企业外部运行。其他应用程序在设计时可能没有考虑到集成,并且很难更改。这些问题和其他类似问题使应用程序集成变得复杂。本章探讨了有助于克服这些挑战的多种集成方法。
Enterprise integration is the task of making disparate applications work together to produce a unified set of functionality. These applications can be custom developed in house or purchased from third-party vendors. They likely run on multiple computers, which may represent multiple platforms, and may be geographically dispersed. Some of the applications may be run outside of the enterprise by business partners or customers. Other applications might not have been designed with integration in mind and are difficult to change. These issues and others like them make application integration complicated. This chapter explores multiple integration approaches that can help overcome these challenges.
什么是良好的应用程序集成?如果集成需求始终相同,那么就只有一种集成方式。然而,与任何复杂的技术工作一样,应用程序集成涉及一系列考虑因素和后果,任何集成机会都应考虑到这些因素和后果。
What makes good application integration? If integration needs were always the same, there would be only one integration style. Yet, like any complex technological effort, application integration involves a range of considerations and consequences that should be taken into account for any integration opportunity.
根本标准是是否使用应用程序集成。如果您可以开发一个不需要与任何其他应用程序协作的独立应用程序,则可以完全避免整个集成问题。但实际上,即使是一个简单的企业也有多个应用程序需要协同工作,以便为企业的员工、合作伙伴和客户提供统一的体验。
The fundamental criterion is whether to use application integration at all. If you can develop a single, standalone application that doesn't need to collaborate with any other applications, you can avoid the whole integration issue entirely. Realistically, though, even a simple enterprise has multiple applications that need to work together to provide a unified experience for the enterprise's employees, partners, and customers.
以下是其他一些主要决策标准。
The following are some other main decision criteria.
应用程序 耦合应用程序应该最大限度地减少彼此之间的依赖性,以便每个应用程序都可以在不给其他应用程序带来问题的情况下发展。正如第 1 章“使用模式解决集成问题”中所解释的,紧密耦合的应用程序对其他应用程序如何工作做出了许多假设;当应用程序发生变化并打破这些假设时,它们之间的集成就会中断。因此,用于集成应用程序的接口应该足够具体以实现有用的功能,但也应该足够通用以允许实现根据需要进行更改。
侵入性 将应用程序 集成,开发人员应努力尽量减少对应用程序的更改以及所需的集成代码量。然而,为了提供良好的集成功能,通常需要进行更改和新代码,并且对应用程序影响最小的方法可能无法提供与企业的最佳集成。
技术选择 不同的集成技术需要不同数量的专用软件和硬件。此类工具可能价格昂贵,可能导致供应商锁定,并且会增加开发人员的学习曲线。另一方面,从头开始创建集成解决方案通常会比最初预期付出更多的努力,并且可能意味着重新发明轮子。
数据格式 集成应用程序必须就它们交换的数据的格式达成一致。更改现有应用程序以使用统一的数据格式可能很困难或不可能。或者,中间转换器可以统一坚持不同数据格式的应用程序。一个相关的问题是数据格式的演变和可扩展性,格式如何随时间变化以及这种变化将如何影响应用程序。
数据时效性 集成应最大限度地缩短一个应用程序决定共享某些数据与其他应用程序拥有该数据之间的时间长度。这可以通过频繁且小块地交换数据来实现。然而,将大量数据分成小块可能会导致效率低下。数据共享的延迟必须考虑到集成设计中。理想情况下,一旦共享数据可供使用,接收器应用程序就应该得到通知。共享时间越长,应用程序不同步的机会就越大,集成也就越复杂。
数据或功能 许多 集成解决方案允许应用程序不仅共享数据,还允许共享功能,因为共享功能可以在应用程序之间提供更好的抽象。尽管调用远程应用程序中的功能可能看起来与调用本地功能相同,但其工作方式却截然不同,这对集成的工作效果产生了重大影响。
远程通信计算机 处理通常是同步的,即过程在其子过程执行时等待。然而,调用远程子过程比本地子过程慢得多,因此过程可能不想等待子过程完成;相反,它可能希望异步调用子过程,即启动子过程但同时继续其自己的处理。异步性可以提供更高效的解决方案,但这样的解决方案的设计、开发和调试也更加复杂。
可靠性 远程 连接不仅速度慢,而且可靠性远低于本地函数调用。当一个过程调用单个应用程序内的子过程时,就假定该子过程可用。远程通信时不一定如此;远程应用程序甚至可能没有运行,或者网络可能暂时不可用。可靠的异步通信使源应用程序能够继续执行其他工作,并确信远程应用程序稍后会采取行动。
Application coupling Integrated applications should minimize their dependencies on each other so that each can evolve without causing problems to the others. As explained in Chapter 1, "Solving Integration Problems Using Patterns," tightly coupled applications make numerous assumptions about how the other applications work; when the applications change and break those assumptions, the integration between them breaks. Therefore, the interfaces for integrating applications should be specific enough to implement useful functionality but general enough to allow the implementation to change as needed.
Intrusiveness When integrating an application into an enterprise, developers should strive to minimize both changes to the application and the amount of integration code needed. Yet, changes and new code are often necessary to provide good integration functionality, and the approaches with the least impact on the application may not provide the best integration into the enterprise.
Technology selection Different integration techniques require varying amounts of specialized software and hardware. Such tools can be expensive, can lead to vendor lock-in, and can increase the learning curve for developers. On the other hand, creating an integration solution from scratch usually results in more effort than originally intended and can mean reinventing the wheel.
Data format Integrated applications must agree on the format of the data they exchange. Changing existing applications to use a unified data format may be difficult or impossible. Alternatively, an intermediate translator can unify applications that insist on different data formats. A related issue is data format evolution and extensibilityhow the format can change over time and how that change will affect the applications.
Data timeliness Integration should minimize the length of time between when one application decides to share some data and other applications have that data. This can be accomplished by exchanging data frequently and in small chunks. However, chunking a large set of data into small pieces may introduce inefficiencies. Latency in data sharing must be factored into the integration design. Ideally, receiver applications should be informed as soon as shared data is ready for consumption. The longer sharing takes, the greater the opportunity for applications to get out of sync and the more complex integration can become.
Data or functionality Many integration solutions allow applications to share not only data but functionality as well, because sharing of functionality can provider better abstraction between the applications. Even though invoking functionality in a remote application may seem the same as invoking local functionality, it works quite differently, with significant consequences for how well the integration works.
Remote Communication Computer processing is typically synchronousthat is, a procedure waits while its subprocedure executes. However, calling a remote subprocedure is much slower than a local one so that a procedure may not want to wait for the subprocedure to complete; instead, it may want to invoke the subprocedure asynchronously, that is, starting the subprocedure but continuing with its own processing simultaneously. Asynchronicity can make for a much more efficient solution, but such a solution is also more complex to design, develop, and debug.
Reliability Remote connections are not only slow, but they are much less reliable than a local function call. When a procedure calls a subprocedure inside a single application, it's a given that the subprocedure is available. This is not necessarily true when communicating remotely; the remote application may not even be running or the network may be temporarily unavailable. Reliable, asynchronous communication enables the source application to go on to other work, confident that the remote application will act sometime later.
因此,正如您所看到的,在选择和设计集成方法时必须考虑几个不同的标准。那么问题就变成了,哪种集成方法最能满足这些标准中的哪一个?
So, as you can see, there are several different criteria that must be considered when choosing and designing an integration approach. The question then becomes, Which integration approach best addresses which of these criteria?
没有一种集成方法能够同等地满足所有标准。因此,随着时间的推移,集成应用程序的多种方法不断发展。各种方法可以概括为四种主要集成风格。
There is no one integration approach that addresses all criteria equally well. Therefore, multiple approaches for integrating applications have evolved over time. The various approaches can be summed up in four main integration styles.
文件传输 让
共享数据库 让
远程过程调用 让 每个
消息传递 让
File Transfer Have each application produce files of shared data for others to consume and consume files that others have produced.
Shared Database Have the applications store the data they wish to share in a common database.
Remote Procedure Invocation Have each application expose some of its procedures so that they can be invoked remotely, and have applications invoke those to initiate behavior and exchange data.
Messaging Have each application connect to a common messaging system, and exchange data and invoke behavior using messages.
本章将每种样式作为模式呈现。这四种模式共享相同的问题陈述——集成应用程序的需要和非常相似的上下文。他们的与众不同之处在于寻求更优雅的解决方案的力量。每一种模式都建立在上一种模式的基础上,寻找一种更复杂的方法来解决其前身的缺点。因此,模式顺序反映了复杂性的增加,但也反映了复杂性的增加。
This chapter presents each style as a pattern. The four patterns share the same problem statementthe need to integrate applicationsand very similar contexts. What differentiates them are the forces searching for a more elegant solution. Each pattern builds on the last, looking for a more sophisticated approach to address the shortcomings of its predecessors. Thus, the pattern order reflects an increasing order of sophistication, but also increasing complexity.
诀窍不是每次都选择一种样式,而是针对特定的集成机会选择最佳样式。每种风格都有其优点和缺点。应用程序可以使用多种样式进行集成,以便每个集成点都能利用最适合它的样式。同样,应用程序可以使用不同的样式与不同的应用程序集成,选择最适合其他应用程序的样式。因此,许多集成方法最好被视为多种集成风格的混合体。为了支持这种类型的集成,许多集成和 EAI 中间件产品采用了多种样式的组合,所有这些样式都有效地隐藏在产品的实现中。
The trick is not to choose one style to use every time but to choose the best style for a particular integration opportunity. Each style has its advantages and disadvantages. Applications may integrate using multiple styles so that each point of integration takes advantage of the style that suits it best. Likewise, an application may use different styles to integrate with different applications, choosing the style that works best for the other application. As a result, many integration approaches can best be viewed as a hybrid of multiple integration styles. To support this type of integration, many integration and EAI middleware products employ a combination of styles, all of which are effectively hidden in the product's implementation.
本书其余部分的模式扩展了消息传递集成风格。我们专注于消息传递,因为我们相信它在集成标准之间提供了良好的平衡,但也是最难使用的风格。因此,消息传递仍然是集成风格中最难理解的技术,也是一种成熟的技术,其模式可以快速解释如何最好地使用它。最后,消息传递是许多商业 EAI 产品的基础,因此解释如何很好地使用消息传递对于教会您如何使用这些产品也大有帮助。本节的重点是强调与应用程序集成相关的问题以及消息传递如何融入其中。
The patterns in the remainder of this book expand on the Messaging integration style. We focus on messaging because we believe that it provides a good balance between the integration criteria but is also the most difficult style to work with. As a result, messaging is still the least well understood of the integration styles and a technology ripe with patterns that quickly explain how to use it best. Finally, messaging is the basis for many commercial EAI products, so explaining how to use messaging well also goes a long way in teaching you how to use those products. The focus of this section is to highlight the issues involved with application integration and how messaging fits into the mix.
马丁·福勒
by Martin Fowler
一个企业有多个独立构建的应用程序,这些应用程序使用不同的语言和平台。
An enterprise has multiple applications that are being built independently, with different languages and platforms.
|
如何集成多个应用程序以便它们协同工作并交换信息? How can I integrate multiple applications so that they work together and can exchange information? |
在理想的世界中,您可能会想象一个组织通过一个单一的、有凝聚力的软件进行操作,该软件从一开始就被设计为以统一和连贯的方式工作。当然,即使是最小的操作也不会这样工作。多个软件处理企业的不同方面。这是由于多种原因造成的。
In an ideal world, you might imagine an organization operating from a single, cohesive piece of software, designed from the beginning to work in a unified and coherent way. Of course, even the smallest operations don't work like that. Multiple pieces of software handle different aspects of the enterprise. This is due to a host of reasons.
人们购买由外部组织开发的软件包。
People buy packages that are developed by outside organizations.
不同的系统在不同的时间构建,导致不同的技术选择。
Different systems are built at different times, leading to different technology choices.
不同的系统是由不同的人构建的,他们的经验和偏好导致他们采用不同的方法来构建应用程序。
Different systems are built by different people whose experience and preferences lead them to different approaches to building applications.
推出应用程序并交付价值比确保解决集成问题更重要,尤其是当集成不会为正在开发的应用程序增加任何价值时。
Getting an application out and delivering value is more important than ensuring that integration is addressed, especially when that integration doesn't add any value to the application under development.
因此,任何组织都必须担心非常不同的应用程序之间共享信息。它们可以基于不同的平台用不同的语言编写,并且对业务运作方式有不同的假设。
As a result, any organization has to worry about sharing information between very divergent applications. These can be written in different languages, based on different platforms, and have different assumptions about how the business operates.
将此类应用程序连接在一起需要彻底了解如何在业务和技术层面上将应用程序连接在一起。如果您最大限度地减少需要了解每个应用程序如何工作的信息,这会容易得多。
Tying together such applications requires a thorough understanding of how to link together applications on both the business and technical levels. This is a lot easier if you minimize what you need to know about how each application works.
我们需要的是一种通用的数据传输机制,可以被多种语言和平台使用,但对每种语言和平台来说都感觉很自然。它应该需要最少量的专用硬件和软件,并利用企业现有的资源。
What is needed is a common data transfer mechanism that can be used by a variety of languages and platforms but that feels natural to each. It should require a minimal amount of specialized hardware and software, making use of what the enterprise already has available.
文件是一种通用存储机制,内置于任何企业操作系统中,并且可通过任何企业语言使用。最简单的方法是以某种方式使用文件集成应用程序。
Files are a universal storage mechanism, built into any enterprise operating system and available from any enterprise language. The simplest approach would be to somehow integrate the applications using files.
|
让每个应用程序生成包含其他应用程序必须使用的信息的文件。集成商负责将文件转换为不同的格式。根据业务性质定期制作文件。 Have each application produce files that contain the information the other applications must consume. Integrators take the responsibility of transforming files into different formats. Produce the files at regular intervals according to the nature of the business. |
文件的一个重要决定是使用什么格式。一个应用程序的输出很少与另一个应用程序所需的完全一致,因此您必须在此过程中对文件进行大量处理。这意味着不仅所有使用文件的应用程序都必须读取该文件,而且您还必须能够对其使用处理工具。因此,标准文件格式随着时间的推移而不断发展。大型机系统通常使用基于 COBOL 文件系统格式的数据源。UNIX 系统使用基于文本的文件。目前的方法是使用XML。围绕这些格式建立了一个由读者、作家和转换工具组成的行业。
An important decision with files is what format to use. Very rarely will the output of one application be exactly what's needed for another, so you'll have to do a fair bit of processing of files along the way. This means not only that all the applications that use a file have to read it, but that you also have to be able to use processing tools on it. As a result, standard file formats have grown up over time. Mainframe systems commonly use data feeds based on the file system formats of COBOL. UNIX systems use text-based files. The current method is to use XML. An industry of readers, writers, and transformation tools has built up around each of these formats.
文件的另一个问题是何时生成和使用它们。由于生成和处理文件需要一定的工作量,因此您通常不想太频繁地使用它们。通常,您有一些定期的业务周期来推动决策:每晚、每周、每季度等等。应用程序会习惯新文件何时可用并及时处理它。
Another issue with files is when to produce them and consume them. Since there's a certain amount of effort required to produce and process a file, you usually don't want to work with them too frequently. Typically, you have some regular business cycle that drives the decision: nightly, weekly, quarterly, and so on. Applications get used to when a new file is available and processes it at its time.
文件的巨大优点是集成商不需要了解应用程序的内部结构。应用团队本身通常会提供该文件。文件的内容和格式是与集成商协商的,尽管如果使用包,选择通常是有限的。然后,集成商处理其他应用程序所需的转换,或者将其留给使用应用程序来决定如何操作和读取文件。因此,不同的应用程序可以很好地相互解耦。每个应用程序都可以自由地进行内部更改,而不会影响其他应用程序,只要它们仍然以相同的格式在文件中生成相同的数据。
The great advantage of files is that integrators need no knowledge of the internals of an application. The application team itself usually provides the file. The file's contents and format are negotiated with integrators, although if a package is used, the choices are often limited. The integrators then deal with the transformations required for other applications, or they leave it up to the consuming applications to decide how they want to manipulate and read the file. As a result, the different applications are quite nicely decoupled from each other. Each application can make internal changes freely without affecting other applications, providing they still produce the same data in the files in the same format. The files effectively become the public interface of each application.
文件传输的一部分简单的是不需要额外的工具或集成包,但这也意味着开发人员必须自己做很多工作。应用程序必须就文件命名约定及其出现的目录达成一致。文件的编写者必须实施一种策略来保持文件名的唯一性。应用程序必须就哪个文件将删除旧文件达成一致,并且负责该责任的应用程序必须知道文件何时过时且不再需要。应用程序需要实现一种锁定机制或遵循计时约定,以确保一个应用程序在另一个应用程序仍在写入文件时不会尝试读取该文件。如果所有应用程序都无法访问同一磁盘,
Part of what makes File Transfer simple is that no extra tools or integration packages are needed, but that also means that developers have to do a lot of the work themselves. The applications must agree on file-naming conventions and the directories in which they appear. The writer of a file must implement a strategy to keep the file names unique. The applications must agree on which one will delete old files, and the application with that responsibility will have to know when a file is old and no longer needed. The applications will need to implement a locking mechanism or follow a timing convention to ensure that one application is not trying to read the file while another is still writing it. If all of the applications do not have access to the same disk, then some application must take responsibility for transferring the file from one disk to another.
文件传输最明显的问题之一是更新往往很少发生,因此系统可能会不同步。客户管理系统可以处理地址变更并每晚生成提取文件,但计费系统可能会在同一天将账单发送到旧地址。有时缺乏同步并不是什么大问题。人们常常预计,即使使用计算机,获取信息也会有一定的滞后。有时,使用陈旧信息的结果是一场灾难。在决定何时生成文件时,必须考虑消费者的新鲜度需求。
One of the most obvious issues with File Transfer is that updates tend to occur infrequently, and as a result systems can get out of synchronization. A customer management system can process a change of address and produce an extract file each night, but the billing system may send the bill to an old address on the same day. Sometimes lack of synchronization isn't a big deal. People often expect a certain lag in getting information around, even with computers. At other times the result of using stale information is a disaster. When deciding on when to produce files, you have to take the freshness needs of consumers into account.
事实上,陈旧的最大问题往往在于软件开发人员本身,他们经常必须处理不太正确的数据。这可能会导致难以解决的不一致。如果一位客户在同一天使用两个不同的系统更改了他的地址,但其中一个系统出错并获取了错误的街道名称,那么您将拥有该客户的两个不同地址。您需要某种方法来找出解决此问题的方法。文件传输之间的时间间隔越长,出现此问题的可能性就越大,也就越令人痛苦。
In fact, the biggest problem with staleness is often on the software development staff themselves, who frequently must deal with data that isn't quite right. This can lead to inconsistencies that are difficult to resolve. If a customer changes his address on the same day with two different systems, but one of them makes an error and gets the wrong street name, you'll have two different addresses for a customer. You'll need some way to figure out how to resolve this. The longer the period between file transfers, the more likely and more painful this problem can become.
当然,您没有理由不能更频繁地生成文件。事实上,您可以将消息传递视为文件传输,您可以在应用程序中的每次更改时生成一个文件。接下来的问题是管理生成的所有文件,确保它们全部被读取并且不会丢失。这超出了基于文件系统的方法的能力范围,特别是因为处理文件会产生昂贵的资源成本,如果您想快速生成大量文件,这可能会令人望而却步。因此,一旦您获得非常细粒度的文件,就更容易将它们视为消息传递。
Of course, there's no reason that you can't produce files more frequently. Indeed, you can think of Messaging as File Transfer where you produce a file with every change in an application. The problem then is managing all the files that get produced, ensuring that they are all read and that none get lost. This goes beyond what file systembased approaches can do, particularly since there are expensive resource costs associated with processing a file, which can get prohibitive if you want to produce lots of files quickly. As a result, once you get to very fine-grained files, it's easier to think of them as Messaging.
为了更快地提供数据并强制执行一组商定的数据格式,请使用共享数据库。要集成应用程序的功能而不是数据,请使用远程过程调用。要频繁交换少量数据(可能用于调用远程功能),请使用消息传递。
To make data available more quickly and enforce an agreed-upon set of data formats, use a Shared Database. To integrate applications' functionality rather than their data, use Remote Procedure Invocation. To enable frequent exchanges of small amounts of data, perhaps used to invoke remote functionality, use Messaging.
马丁·福勒
by Martin Fowler
一个企业有多个独立构建的应用程序,这些应用程序使用不同的语言和平台。企业需要快速、一致地共享信息。
An enterprise has multiple applications that are being built independently, with different languages and platforms. The enterprise needs information to be shared rapidly and consistently.
|
如何集成多个应用程序以便它们协同工作并交换信息? How can I integrate multiple applications so that they work together and can exchange information? |
文件传输使如果更改不能快速通过一系列应用程序,您可能会因数据过时而犯错误。对于现代企业来说,每个人都必须拥有最新的数据。这不仅减少了错误,还增加了人们对数据本身的信任。
File Transfer enables applications to share data, but it can lack timelinessyet timeliness of integration is often critical. If changes do not quickly work their way through a family of applications, you are likely to make mistakes due to the staleness of the data. For modern businesses, it is imperative that everyone have the latest data. This not only reduces errors, but also increases people's trust in the data itself.
快速更新还可以更好地处理不一致的情况。同步越频繁,出现不一致的可能性就越小,处理它们的工作量就越少。但无论变化多么快,仍然会出现问题。如果一个地址快速连续更新且不一致,您如何确定哪个是真实地址?您可以获取每条数据并说一个应用程序是该数据的主源,但是您必须记住哪个应用程序是哪个数据的主源。
Rapid updates also allow inconsistencies to be handled better. The more frequently you synchronize, the less likely you are to get inconsistencies and the less effort they are to deal with. But however rapid the changes, there are still going to be problems. If an address is updated inconsistently in rapid succession, how do you decide which one is the true address? You could take each piece of data and say that one application is the master source for that data, but then you'd have to remember which application is the master for which data.
文件传输也集成中的许多问题都源于查看数据的方式不兼容。通常,这些代表了可能产生巨大影响的微妙业务问题。地质数据库可以将油井定义为可能产油也可能不产油的单个钻孔。生产数据库可以定义由单件设备覆盖的多个孔。这些语义不一致的情况比不一致的数据格式更难处理。(为了对这些问题进行更深入的讨论,确实值得阅读《数据与现实》[肯特].) 需要的是一个所有应用程序共享的中央一致数据存储,以便每个应用程序在需要时都可以访问任何共享数据。
File Transfer also may not enforce data format sufficiently. Many of the problems in integration come from incompatible ways of looking at the data. Often these represent subtle business issues that can have a huge effect. A geological database may define an oil well as a single drilled hole that may or may not produce oil. A production database may define a well as multiple holes covered by a single piece of equipment. These cases of semantic dissonance are much harder to deal with than inconsistent data formats. (For a much deeper discussion of these issues, it's really worth reading Data and Reality [Kent].) What is needed is a central, agreed-upon datastore that all of the applications share so each has access to any of the shared data whenever it needs it.
|
通过将应用程序的数据存储在单个共享数据库中来集成应用程序,并定义数据库的架构来处理不同应用程序的所有需求。 Integrate applications by having them store their data in a single Shared Database, and define the schema of the database to handle all the needs of the different applications. |
如果一系列集成应用程序都依赖于同一个数据库,那么您可以非常确定它们始终保持一致。如果您确实从不同来源同时更新了单个数据,那么您拥有的事务管理系统可以尽可能优雅地处理该数据。由于更新之间的时间间隔很短,因此更容易发现和修复任何错误。
If a family of integrated applications all rely on the same database, then you can be pretty sure that they are always consistent all of the time. If you do get simultaneous updates to a single piece of data from different sources, then you have transaction management systems that handle that about as gracefully as it ever can be managed. Since the time between updates is so small, any errors are much easier to find and fix.
基于 SQL 的关系数据库的广泛使用使共享数据库变得更加容易。几乎所有应用程序开发平台都可以使用 SQL,通常需要使用相当复杂的工具。因此您不必担心多种文件格式。由于任何应用程序几乎都必须使用 SQL,因此这避免了添加另一种让每个人都需要掌握的技术。
Shared Database is made much easier by the widespread use of SQL-based relational databases. Pretty much all application development platforms can work with SQL, often with quite sophisticated tools. So you don't have to worry about multiple file formats. Since any application pretty much has to use SQL anyway, this avoids adding yet another technology for everyone to master.
由于每个应用程序都使用相同的数据库,因此这就消除了语义不一致的问题。不要让这些问题恶化,直到难以通过转换来解决,而是被迫在软件上线并收集大量不兼容数据之前面对它们并处理它们。
Since every application is using the same database, this forces out problems in semantic dissonance. Rather than leaving these problems to fester until they are difficult to solve with transforms, you are forced to confront them and deal with them before the software goes live and you collect large amounts of incompatible data.
共享数据库的最大困难之一是为共享数据库提供合适的设计。提出一个可以满足多个应用程序需求的统一模式是一项非常困难的工作,通常会导致应用程序程序员难以使用该模式。如果设计统一模式的技术困难还不够,那么还存在严重的政治困难。如果关键应用程序可能会因使用统一模式而遭受延迟,那么通常会面临不可抗拒的分离压力。部门之间的人为冲突往往会加剧这个问题。
One of the biggest difficulties with Shared Database is coming up with a suitable design for the shared database. Coming up with a unified schema that can meet the needs of multiple applications is a very difficult exercise, often resulting in a schema that application programmers find difficult to work with. And if the technical difficulties of designing a unified schema aren't enough, there are also severe political difficulties. If a critical application is likely to suffer delays in order to work with a unified schema, then often there is irresistible pressure to separate. Human conflicts between departments often exacerbate this problem.
对共享数据库的另一个更严格的限制是外部包。大多数打包应用程序无法使用除其自身架构之外的架构。即使有一定的适应空间,也可能比集成商希望的要有限得多。更糟糕的是,软件供应商通常保留在每个新版本的软件中更改架构的权利。
Another, harder limit to Shared Database is external packages. Most packaged applications won't work with a schema other than their own. Even if there is some room for adaptation, it's likely to be much more limited than integrators would like. Adding to the problem, software vendors usually reserve the right to change the schema with every new release of the software.
这个问题还延伸到开发后的整合。即使您可以组织所有应用程序,如果发生公司合并,您仍然会遇到集成问题。
This problem also extends to integration after development. Even if you can organize all your applications, you still have an integration problem should a merger of companies occur.
使用共享数据库的多个应用程序频繁读取和修改相同的数据可能会将数据库变成性能瓶颈,并且可能会导致死锁,因为每个应用程序都将其他应用程序锁定在数据之外。当应用程序分布在多个位置时,通过广域网访问单个共享数据库通常速度太慢而不实用。分布式数据库也允许每个应用程序通过本地网络连接访问数据库,但混淆了数据应该存储在哪台计算机上的问题。具有锁定冲突的分布式数据库很容易成为性能噩梦。
Multiple applications using a Shared Database to frequently read and modify the same data can turn the database into a performance bottleneck and can cause deadlocks as each application locks others out of the data. When applications are distributed across multiple locations, accessing a single, shared database across a wide-area network is typically too slow to be practical. Distributing the database as well allows each application to access the database via a local network connection, but confuses the issue of which computer the data should be stored on. A distributed database with locking conflicts can easily become a performance nightmare.
要集成应用程序的功能而不是数据,请使用远程过程调用。要使用每种数据类型的格式而不是一种通用模式来频繁交换少量数据,请使用Messaging 。
To integrate applications' functionality rather than their data, use Remote Procedure Invocation. To enable frequent exchanges of small amounts of data using a format per datatype rather than one universal schema, use Messaging.
马丁·福勒
by Martin Fowler
一个企业有多个独立构建的应用程序,这些应用程序使用不同的语言和平台。企业需要以响应方式共享数据和流程。
An enterprise has multiple applications that are being built independently, with different languages and platforms. The enterprise needs to share data and processes in a responsive way.
|
如何集成多个应用程序以便它们协同工作并交换信息? How can I integrate multiple applications so that they work together and can exchange information? |
文件传输和共享数据库使数据更改通常需要跨不同应用程序采取操作。例如,更改地址可能是简单的数据更改,也可能触发注册和法律程序,以考虑不同法律管辖区的不同规则。让一个应用程序直接在其他应用程序中调用此类进程将需要应用程序对其他应用程序的内部了解太多。
File Transfer and Shared Database enable applications to share their data, which is an important part of application integration, but just sharing data is often not enough. Changes in data often require actions to be taken across different applications. For example, changing an address may be a simple change in data, or it may trigger registration and legal processes to take into account different rules in different legal jurisdictions. Having one application invoke such processes directly in others would require applications to know far too much about the internals of other applications.
这个问题反映了应用程序设计中的一个经典困境。应用程序设计中最强大的结构化机制之一是封装,其中模块通过函数调用接口隐藏其数据。通过这种方式,他们可以拦截数据的变化,以在数据变化时执行他们需要执行的各种操作。共享数据库提供了一个大型的、未封装的数据结构,这使得做到这一点变得更加困难。文件传输允许应用程序在处理文件时对更改做出反应,但该过程会被延迟。
This problem mirrors a classic dilemma in application design. One of the most powerful structuring mechanisms in application design is encapsulation, where modules hide their data through a function call interface. In this way, they can intercept changes in data to carry out the various actions they need to perform when the data is changed. Shared Database provides a large, unencapsulated data structure, which makes it much harder to do this. File Transfer allows an application to react to changes as it processes the file, but the process is delayed.
共享数据库具有未封装的数据这一事实也使得维护一系列集成应用程序变得更加困难。任何应用程序中的许多更改都可以触发数据库中的更改,并且数据库更改会对每个应用程序产生相当大的连锁反应。因此,使用共享数据库的组织通常非常不愿意更改数据库,这意味着应用程序开发工作对业务不断变化的需求的响应能力要差得多。
The fact that Shared Database has unencapsulated data also makes it more difficult to maintain a family of integrated applications. Many changes in any application can trigger a change in the database, and database changes have a considerable ripple effect through every application. As a result, organizations that use Shared Database are often very reluctant to change the database, which means that the application development work is much less responsive to the changing needs of the business.
需要一种机制,让一个应用程序调用另一个应用程序中的函数,传递需要共享的数据并调用告诉接收方应用程序如何处理数据的函数。
What is needed is a mechanism for one application to invoke a function in another application, passing the data that needs to be shared and invoking the function that tells the receiver application how to process the data.
|
将每个应用程序开发为具有封装数据的大型对象或组件。提供接口以允许其他应用程序与正在运行的应用程序进行交互。 Develop each application as a large-scale object or component with encapsulated data. Provide an interface to allow other applications to interact with the running application. |
远程过程调用将封装原则应用于集成应用程序。如果一个应用程序需要另一个应用程序拥有的某些信息,它会直接询问该应用程序。如果一个应用程序需要修改另一个应用程序的数据,它可以通过调用另一个应用程序来实现。这允许每个应用程序维护其拥有的数据的完整性。此外,每个应用程序都可以更改其内部数据的格式,而不会影响其他所有应用程序。
Remote Procedure Invocation applies the principle of encapsulation to integrating applications. If an application needs some information that is owned by another application, it asks that application directly. If one application needs to modify the data of another, it does so by making a call to the other application. This allows each application to maintain the integrity of the data it owns. Furthermore, each application can alter the format of its internal data without affecting every other application.
许多技术(例如 CORBA、COM、.NET Remoting 和 Java RMI)都实现了远程过程调用(也称为远程过程调用或 RPC)。这些方法因支持它们的系统数量及其易用性而异。这些环境通常会添加额外的功能,例如事务。由于其普遍性,当前最受欢迎的是使用 SOAP 和 XML 等标准的 Web 服务。Web 服务的一个特别有价值的特性是它们可以轻松地与 HTTP 配合使用,而 HTTP 很容易通过防火墙。
A number of technologies, such as CORBA, COM, .NET Remoting, and Java RMI, implement Remote Procedure Invocation (also referred to as Remote Procedure Call, or RPC). These approaches vary as to how many systems support them and their ease of use. Often these environments add additional capabilities, such as transactions. For sheer ubiquity, the current favorite is Web services, using standards such as SOAP and XML. A particularly valuable feature of Web services is that they work easily with HTTP, which is easy to get through firewalls.
事实上,有一些方法可以包装数据,这使得处理语义不一致变得更加容易。应用程序可以为相同的数据提供多个接口,允许某些客户端看到一种样式,而其他客户端则看到不同的样式。即使更新也可以使用多个接口。与关系视图相比,这提供了更多支持多种观点的能力。然而,集成商添加转换组件很困难,因此每个应用程序都必须与其邻居协商其接口。
The fact that there are methods that wrap the data makes it easier to deal with semantic dissonance. Applications can provide multiple interfaces to the same data, allowing some clients to see one style and others a different style. Even updates can use multiple interfaces. This provides a lot more ability to support multiple points of view than can be achieved by relational views. However, it is awkward for integrators to add transformation components, so each application has to negotiate its interface with its neighbors.
由于软件开发人员习惯于过程调用,因此远程过程调用非常适合他们已经习惯的内容。事实上,这与其说是优点,不如说是缺点。远程和本地过程调用在性能和可靠性方面存在很大差异。如果人们不理解这些,那么远程过程调用可能会导致系统缓慢且不可靠(请参阅 [Waldo ]、[ EAA ]) 。
Since software developers are used to procedure calls, Remote Procedure Invocation fits in nicely with what they are already used to. Actually, this is more of a disadvantage than an advantage. There are big differences in performance and reliability between remote and local procedure calls. If people don't understand these, then Remote Procedure Invocation can lead to slow and unreliable systems (see [Waldo], [EAA]).
尽管封装通过消除大型共享数据结构来帮助减少应用程序的耦合,但应用程序仍然相当紧密地耦合在一起。每个系统支持的远程调用往往会将不同的系统打成一个越来越大的结。特别是,按特定顺序执行某些操作可能会导致独立更改系统变得困难。之所以会出现这些类型的问题,是因为在单个应用程序中并不重要的问题在集成应用程序时变得非常重要。人们经常以设计单个应用程序的方式来设计集成,却没有意识到参与规则发生了巨大的变化。
Although encapsulation helps reduce the coupling of the applications by eliminating a large shared data structure, the applications are still fairly tightly coupled together. The remote calls that each system supports tend to tie the different systems into a growing knot. In particular, sequencingdoing certain things in a particular ordercan make it difficult to change systems independently. These types of problems often arise because issues that aren't significant within a single application become so when integrating applications. People often design the integration the way they would design a single application, unaware that the rules of the engagement change dramatically.
要以更松散耦合的异步方式集成应用程序,请使用消息传递来实现少量数据的频繁交换,这些数据可能用于调用远程功能。
To integrate applications in a more loosely coupled, asynchronous fashion, use Messaging to enable frequent exchanges of small amounts of data, ones that are perhaps used to invoke remote functionality.
一个企业有多个独立构建的应用程序,这些应用程序使用不同的语言和平台。企业需要以响应方式共享数据和流程。
An enterprise has multiple applications that are being built independently, with different languages and platforms. The enterprise needs to share data and processes in a responsive way.
|
如何集成多个应用程序以便它们协同工作并交换信息? How can I integrate multiple applications so that they work together and can exchange information? |
文件传输和共享数据库使远程过程调用使通常,集成的挑战在于尽可能及时地在不同的系统之间进行协作,而不是将系统耦合在一起,以免它们在应用程序执行或应用程序开发方面变得不可靠。
File Transfer and Shared Database enable applications to share their data but not their functionality. Remote Procedure Invocation enables applications to share functionality, but it tightly couples them as well. Often the challenge of integration is about making collaboration between separate systems as timely as possible, without coupling systems together in such a way that they become unreliable either in terms of application execution or application development.
文件传输允许系统无法跟上彼此的步伐。协作行为太慢了。共享数据库以响应方式将数据保存它也无法处理协作行为。
File Transfer allows you to keep the applications well decoupled but at the cost of timeliness. Systems just can't keep up with each other. Collaborative behavior is way too slow. Shared Database keeps data together in a responsive way but at the cost of coupling everything to the database. It also fails to handle collaborative behavior.
面对这些问题,远程过程调用似乎是一个有吸引力的选择。但是将单一应用程序模型扩展到应用程序集成会弥补许多其他弱点。这些弱点始于分布式开发的本质问题。尽管 RPC 看起来像本地调用,但它们的行为方式并不相同。远程调用速度较慢,而且更有可能失败。当多个应用程序在企业中进行通信时,您不希望一个应用程序发生故障而导致所有其他应用程序瘫痪。此外,您不想设计一个假设调用速度很快的系统,并且您不希望每个应用程序了解其他应用程序的详细信息,
Faced with these problems, Remote Procedure Invocation seems an appealing choice. But extending a single application model to application integration dredges up plenty of other weaknesses. These weaknesses start with the essential problems of distributed development. Despite that RPCs look like local calls, they don't behave the same way. Remote calls are slower, and they are much more likely to fail. With multiple applications communicating across an enterprise, you don't want one application's failure to bring down all of the other applications. Also, you don't want to design a system assuming that calls are fast, and you don't want each application knowing the details about other applications, even if it's only details about their interfaces.
我们需要的是像文件传输这样的东西,其中可以快速生成大量小数据包并轻松传输,并且当有新数据包可供使用时,接收器应用程序会自动收到通知。传输需要重试机制以确保其成功。用于存储数据的任何磁盘结构或数据库的详细信息都需要对应用程序隐藏,以便与共享数据库不同,可以轻松更改存储架构和详细信息以反映企业不断变化的需求。一个应用程序应该能够将数据包发送到另一个应用程序以调用另一个应用程序中的行为,例如远程过程调用,但不易失败。数据传输应该是异步的,以便发送方不需要等待接收方,特别是在需要重试时。
What we need is something like File Transfer in which lots of little data packets can be produced quickly and transferred easily, and the receiver application is automatically notified when a new packet is available for consumption. The transfer needs a retry mechanism to make sure it succeeds. The details of any disk structure or database for storing the data needs to be hidden from the applications so that, unlike Shared Database, the storage schema and details can be easily changed to reflect the changing needs of the enterprise. One application should be able to send a packet of data to another application to invoke behavior in the other application, like Remote Procedure Invocation, but without being prone to failure. The data transfer should be asynchronous so that the sender does not need to wait on the receiver, especially when retry is necessary.
|
使用消息传递以可定制的格式频繁、立即、可靠、异步地传输数据包。 Use Messaging to transfer packets of data frequently, immediately, reliably, and asynchronously, using customizable formats. |
异步消息传递从根本上来说是对分布式系统问题的务实反应。发送消息不需要两个系统同时启动并准备就绪。此外,以异步方式思考通信迫使开发人员认识到使用远程应用程序的速度较慢,这鼓励设计具有高内聚性(大量本地工作)和低粘附性(远程选择性工作)的组件。
Asynchronous messaging is fundamentally a pragmatic reaction to the problems of distributed systems. Sending a message does not require both systems to be up and ready at the same time. Furthermore, thinking about the communication in an asynchronous manner forces developers to recognize that working with a remote application is slower, which encourages design of components with high cohesion (lots of work locally) and low adhesion (selective work remotely).
消息传递系统还允许在使用文件传输时实现大部分解耦。 消息可以在传输过程中进行转换,而发送者或接收者都不知道该转换。这种解耦允许集成商在向多个接收器广播消息、将消息路由到多个接收器之一或其他拓扑之间进行选择。这将集成决策与应用程序开发分开。由于人类问题往往将应用程序开发与应用程序集成分开,因此这种方法符合人性而不是违背人性。
Messaging systems also allow much of the decoupling you get when using File Transfer. Messages can be transformed in transit without either the sender or receiver knowing about the transformation. The decoupling allows integrators to choose between broadcasting messages to multiple receivers, routing a message to one of many receivers, or other topologies. This separates integration decisions from the development of the applications. Since human issues tend to separate application development from application integration, this approach works with human nature rather than against it.
这种转换意味着不同的应用程序可以具有完全不同的概念模型。当然,这意味着会出现语义不一致。然而,消息传递的观点是,共享数据库用于避免语义不一致的措施过于复杂,无法在实践中发挥作用。此外,第三方应用程序以及作为公司合并的一部分添加的应用程序也会出现语义不一致,因此消息传递方法是解决该问题,而不是设计应用程序来避免该问题。
The transformation means that separate applications can have quite different conceptual models. Of course, this means that semantic dissonance will occur. However, the messaging viewpoint is that the measures used by Shared Database to avoid semantic dissonance are too complicated to work in practice. Also, semantic dissonance is going to occur with third-party applications and with applications added as part of a corporate merger, so the messaging approach is to address the issue rather than design applications to avoid it.
通过频繁发送小消息,您还允许应用程序进行行为协作以及共享数据。如果收到保险索赔后需要启动流程,则可以通过在收到单个索赔时发送消息来立即完成。可以请求信息并快速做出答复。虽然这种协作不会像远程过程调用那么快,但在处理消息和返回响应时,调用者不需要停止。而且消息传递并不像许多人想象的那么慢许多消息传递解决方案起源于金融服务行业,该行业每秒有数以千计的股票报价或交易必须通过消息传递系统。
By sending small messages frequently, you also allow applications to collaborate behaviorally as well as share data. If a process needs to be launched once an insurance claim is received, it can be done immediately by sending a message when a single claim comes in. Information can be requested and a reply made rapidly. While such collaboration isn't going to be as fast as Remote Procedure Invocation, the caller needn't stop while the message is being processed and the response returned. And messaging isn't as slow as many people thinkmany messaging solutions originated in the financial services industry where thousands of stock quotes or trades have to pass through a messaging system every second.
本书是关于消息传递的,因此您可以放心地假设我们认为消息传递通常是企业应用程序集成的最佳方法。但是,您不应该假设它没有问题。消息传递中消息的高频率减少了困扰文件传输的许多不一致问题,但它并没有完全删除它们。由于系统更新不完全同步,仍然会存在一些滞后问题。异步设计并不是大多数软件人员所接受的教学方式,因此存在许多不同的规则和技术。消息传递上下文使得这比在 X Windows 等异步应用程序环境中编程要容易一些,但异步仍然有一个学习曲线。在这种环境中测试和调试也更加困难。
This book is about Messaging, so you can safely assume that we consider Messaging to be generally the best approach to enterprise application integration. You should not assume, however, that it is free of problems. The high frequency of messages in Messaging reduces many of the inconsistency problems that bedevil File Transfer, but it doesn't remove them entirely. There are still going to be some lag problems with systems not being updated quite simultaneously. Asynchronous design is not the way most software people are taught, and as a result there are many different rules and techniques in place. The messaging context makes this a bit easier than programming in an asynchronous application environment like X Windows, but asynchrony still has a learning curve. Testing and debugging are also harder in this environment.
转换消息的能力有一个很好的好处,即允许应用程序彼此之间比远程过程调用更加解耦。但这种独立性确实意味着集成商通常需要编写大量混乱的粘合代码来将所有内容组合在一起。
The ability to transform messages has the nice benefit of allowing applications to be much more decoupled from each other than in Remote Procedure Invocation. But this independence does mean that integrators are often left with writing a lot of messy glue code to fit everything together.
一旦您决定要使用消息传递进行系统集成,就有许多新问题需要考虑和可以采用的实践。
Once you decide that you want to use Messaging for system integration, there are a number of new issues to consider and practices you can employ.
如何传输数据包?
How do you transfer packets of data?
发送方通过连接发送方和接收方的消息通道发送消息,将数据发送到接收方。
A sender sends data to a receiver by sending a Message via a Message Channel that connects the sender and receiver.
您如何知道将数据发送到哪里?
How do you know where to send the data?
如果发送方不知道在哪里寻址数据,它可以将数据发送到消息路由器,消息路由器会将数据定向到正确的接收方。
If the sender does not know where to address the data, it can send the data to a Message Router, which will direct the data to the proper receiver.
您如何知道要使用什么数据格式?
How do you know what data format to use?
如果发送方和接收方对数据格式不一致,则发送方可以将数据定向到消息转换器,将数据转换为接收方的格式,然后将数据转发到接收方。
If the sender and receiver do not agree on the data format, the sender can direct the data to a Message Translator that will convert the data to the receiver's format and then forward the data to the receiver.
如果您是应用程序开发人员,如何将应用程序连接到消息传递系统?
If you're an application developer, how do you connect your application to the messaging system?
希望使用消息传递的应用程序将实现消息端点来执行实际的发送和接收。
An application that wishes to use messaging will implement Message Endpoints to perform the actual sending and receiving.
在第 2 章“集成样式”中,我们讨论了应用程序相互连接的各种选项,包括消息传递。消息传递通过异步通信使应用程序松散耦合,这也使通信更加可靠,因为两个应用程序不必同时运行。消息传递使消息传递系统负责将数据从一个应用程序传输到另一个应用程序,因此应用程序可以专注于需要共享哪些数据,而不是如何共享数据。
In Chapter 2, "Integration Styles," we discussed the various options for connecting applications with one another, including Messaging. Messaging makes applications loosely coupled by communicating asynchronously, which also makes the communication more reliable because the two applications do not have to be running at the same time. Messaging makes the messaging system responsible for transferring data from one application to another, so the applications can focus on what data they need to share as opposed to how to share it.
与大多数技术一样,消息传递涉及某些基本概念。一旦理解了这些概念,您甚至可以在了解有关如何使用该技术的所有细节之前就理解该技术。以下是基本消息传递概念。
Like most technologies, Messaging involves certain basic concepts. Once you understand these concepts, you can make sense of the technology even before you understand all of the details about how to use it. The following are the basic messaging concepts.
通道 消息传递应用程序通过消息通道(将发送方连接到接收方的虚拟管道)传输数据。新安装的消息系统通常不包含任何通道;您必须确定您的应用程序需要如何进行通信,然后创建通道来促进它。
消息 消息 是可以在通道上传输的原子数据包。因此,要传输数据,应用程序必须将数据分成一个或多个数据包,将每个数据包包装为一条消息,然后在通道上发送该消息。同样,接收者应用程序接收消息并且必须从消息中提取数据来处理它。消息系统将重复尝试传递消息(例如,将其从发送者传输到接收者),直到成功为止。
管道和过滤器 在 最简单的情况下,消息传递系统将消息直接从发送者的计算机传递到接收者的计算机。然而,在消息由原始发送者发送之后但在最终接收者接收之前,通常需要对消息执行某些操作。例如,由于接收方期望的消息格式与发送方的消息格式不同,因此可能必须验证或转换消息。管道和过滤器架构描述了如何使用通道将多个处理步骤链接在一起。
路由 在 具有众多应用程序和连接它们的通道的大型企业中,消息可能必须经过多个通道才能到达其最终目的地。消息必须遵循的路线可能非常复杂,以至于原始发送者不知道哪个通道会将消息发送给最终接收者。相反,原始发送者将消息发送到消息路由器,这是一个应用程序组件,它取代了管道和过滤器架构中的过滤器。然后,路由器确定如何导航通道拓扑并将消息定向到最终接收者,或至少定向到下一个路由器。
转换 不同的应用程序可能不会就相同概念数据的格式达成一致;发送方以一种方式格式化消息,但接收方希望以另一种方式格式化消息。为了协调这一点,消息必须经过中间过滤器,即消息转换器,它将消息从一种格式转换为另一种格式。
端点 大多数 应用程序没有任何与消息传递系统交互的内置功能。相反,它们必须包含一层代码,该代码层既知道应用程序如何工作,又知道消息传递系统如何工作,从而将两者联系起来,以便它们协同工作。该桥接代码是一组协调的消息端点,使应用程序能够发送和接收消息。
Channels Messaging applications transmit data through a Message Channel, a virtual pipe that connects a sender to a receiver. A newly installed messaging system typically doesn't contain any channels; you must determine how your applications need to communicate and then create the channels to facilitate it.
Messages A Message is an atomic packet of data that can be transmitted on a channel. Thus, to transmit data, an application must break the data into one or more packets, wrap each packet as a message, and then send the message on a channel. Likewise, a receiver application receives a message and must extract the data from the message to process it. The message system will try repeatedly to deliver the message (e.g., transmit it from the sender to the receiver) until it succeeds.
Pipes and Filters In the simplest case, the messaging system delivers a message directly from the sender's computer to the receiver's computer. However, certain actions often need to be performed on the message after it is sent by its original sender but before it is received by its final receiver. For example, the message may have to be validated or transformed because the receiver expects a message format different from the sender's. The Pipes and Filters architecture describes how multiple processing steps can be chained together using channels.
Routing In a large enterprise with numerous applications and channels to connect them, a message may have to go through several channels to reach its final destination. The route a message must follow may be so complex that the original sender does not know what channel will get the message to the final receiver. Instead, the original sender sends the message to a Message Router, an application component that takes the place of a filter in the Pipes and Filters architecture. The router then determines how to navigate the channel topology and directs the message to the final receiver, or at least to the next router.
Transformation Various applications may not agree on the format for the same conceptual data; the sender formats the message one way, but the receiver expects it to be formatted another way. To reconcile this, the message must go through an intermediate filter, a Message Translator, which converts the message from one format to another.
Endpoints Most applications do not have any built-in capability to interface with a messaging system. Rather, they must contain a layer of code that knows both how the application works and how the messaging system works, bridging the two so that they work together. This bridge code is a set of coordinated Message Endpoints that enable the application to send and receive messages.
本章中的模式为您提供了如何使用消息传递实现企业集成的基本词汇和理解。后续的每一章都建立在本章的基本模式之一之上,并更深入地涵盖该特定主题。
The patterns in this chapter provide you with the basic vocabulary and understanding of how to achieve enterprise integration using Messaging. Each subsequent chapter builds on one of the base patterns in this chapter and covers that particular topic in more depth.
根模式和章节的关系
Relationship of Root Patterns and Chapters
您可以直接阅读本章,以了解消息传递中主要主题的概述。有关任一主题的更多详细信息,请跳至与该特定模式相关的章节。
You can read this chapter straight through for an overview of the main topics in Messaging. For more details about any one of these topics, skip ahead to the chapter associated with that particular pattern.
企业有两个独立的应用程序需要使用消息传递进行通信。
An enterprise has two separate applications that need to communicate by using Messaging.
|
一个应用程序如何使用消息传递与另一个应用程序进行通信? How does one application communicate with another using messaging? |
一旦一组应用程序具有可用的消息传递系统,人们很容易认为任何应用程序都可以在您希望的任何时候与任何其他应用程序进行通信。然而,消息传递系统并不能神奇地连接所有应用程序。
Once a group of applications has a messaging system available, it's tempting to think that any application can communicate with any other application anytime you want it to. Yet, the messaging system does not magically connect all of the applications.
应用程序神奇地连接
Applications Magically Connected
同样,应用程序并不只是随机地将信息扔到消息传递系统中,而其他应用程序只是随机抓取它们遇到的任何信息。(即使这有效,效率也不会很高。)相反,发送信息的应用程序知道它是什么类型的信息,并且想要接收信息的应用程序不仅仅寻找任何信息,而是寻找任何信息。他们可以使用的特定类型的信息。因此,消息传递系统并不是一个供应用程序将信息放入或从中取出信息的大桶。它是一组连接,使应用程序能够通过以预定的、可预测的方式传输信息来进行通信。
Likewise, it's not as though an application just randomly throws out information into the messaging system while other applications just randomly grab whatever information they run across. (Even if this worked, it wouldn't be very efficient.) Rather, the application sending out the information knows what sort of information it is, and the applications that would like to receive information aren't looking for just any information but for particular types of information they can use. So the messaging system isn't a big bucket that applications throw information into and pull information out of. It's a set of connections that enables applications to communicate by transmitting information in predetermined, predictable ways.
|
使用消息通道连接应用程序,其中一个应用程序将信息写入通道,另一个应用程序从通道读取该信息。 Connect the applications using a Message Channel, where one application writes information to the channel and the other one reads that information from the channel. |
当应用程序有信息需要通信时,它不仅仅将信息扔到消息传递系统中,而是将信息添加到特定的消息通道中。接收信息的应用程序并不只是从消息传递系统中随机获取信息;而是从消息传递系统中随机获取信息。它从特定的消息通道检索信息。
When an application has information to communicate, it doesn't just fling the information into the messaging system but adds the information to a particular Message Channel. An application receiving information doesn't just pick it up at random from the messaging system; it retrieves the information from a particular Message Channel.
发送信息的应用程序不一定知道哪个特定应用程序最终将检索该信息,但可以确保检索该信息的应用程序对该信息感兴趣。这是因为消息系统有不同的消息通道用于应用程序想要通信的不同类型的信息。当应用程序发送信息时,它不会随机地将信息添加到任何可用的通道;它将其添加到一个通道,其特定目的是传达此类信息。同样,想要接收特定信息的应用程序不会从某个随机通道获取信息;而是会从某些随机通道中获取信息。它根据需要的信息类型选择从哪个渠道获取信息。
The application sending information doesn't necessarily know what particular application will end up retrieving it, but it can be assured that the application that retrieves the information is interested in the information. This is because the messaging system has different Message Channels for different types of information the applications want to communicate. When an application sends information, it doesn't randomly add the information to any channel available; it adds it to a channel whose specific purpose is to communicate that sort of information. Likewise, an application that wants to receive particular information doesn't pull info off some random channel; it selects what channel to get information from based on what type of information it wants.
通道是消息系统中的逻辑地址。它们的实际实现方式取决于消息传递系统产品及其实现。也许每个消息端点都与每个其他端点有直接连接,或者它们可能都通过中央集线器连接。也许几个单独的逻辑通道被配置为一个物理通道,但仍然可以保持哪些消息要发送到哪个目的地。定义的逻辑通道集对应用程序隐藏了这些配置详细信息。
Channels are logical addresses in the messaging system. How they're actually implemented depends on the messaging system product and its implementation. Perhaps every Message Endpoint has a direct connection to every other endpoint, or perhaps they're all connected through a central hub. Perhaps several separate logical channels are configured as one physical channel that nevertheless keeps straight which messages are intended for which destination. The set of defined logical channels hides these configuration details from the applications.
消息传递系统不会自动预先配置应用程序通信所需的所有消息通道。相反,设计应用程序和应用程序之间通信的开发人员必须决定他们需要什么渠道进行通信。然后,安装消息系统软件的系统管理员还必须对其进行配置,以设置应用程序期望的通道。尽管某些消息传递系统实现支持在应用程序运行时创建新通道,但这并不是很有用,因为除了创建通道的应用程序之外,其他应用程序也必须了解新通道,以便它们也可以开始使用它。因此,可用信道的数量和用途往往在部署时是固定的。第 4 章,“消息传递渠道。”)
A messaging system doesn't automatically come preconfigured with all of the message channels the applications need to communicate. Rather, the developers designing the applications and the communication between them have to decide what channels they need for the communication. Then the system administrator who installs the messaging system software must also configure it to set up the channels that the applications expect. Although some messaging system implementations support creating new channels while the applications are running, this isn't very useful because other applications besides the one that creates the channel must know about the new channel so they can start using it too. Thus, the number and purpose of channels available tend to be fixed at deployment time. (There are exceptions to this rule; see the introduction to Chapter 4, "Messaging Channels.")
一些消息传递词汇A Little Bit of Messaging Vocabulary那么我们如何称呼通过消息通道进行通信的应用程序呢?有许多术语基本上是相同的。最通用的术语可能是发送者和接收者;应用程序将消息发送到消息通道以由另一个应用程序接收。其他流行的术语是生产者和消费者。 您还会看到发布者和订阅者,但它们更适合发布-订阅通道,并且通常以通用形式使用。有时我们说应用程序监听在另一个应用程序与之通信的通道上。在 Web 服务领域,我们通常谈论请求者和提供者。这些术语通常意味着请求者向提供者发送消息并接收返回的响应。在过去,我们将这些称为客户端和服务器(这些术语是等效的,但说“客户端”和“服务器”并不酷)。 So what do we call the applications that communicate via a Message Channel? There are a number of terms out there that are largely equivalent. The most generic terms are probably sender and receiver; an application sends a message to a Message Channel to be received by another application. Other popular terms are producer and consumer. You will also see publisher and subscriber, but they are geared more toward Publish-Subscribe Channels and are often used in generic form. Sometimes we say that an application listens on a channel to which another application talks. In the world of Web services, we generally talk about a requester and a provider. These terms usually imply that the requester sends a message to the provider and receives a response back. In the olden days we called these client and server (the terms are equivalent, but saying "client" and "server" is not cool). 现在它变得令人困惑。在处理 Web 服务时,向服务提供者发送消息的应用程序通常被称为服务的使用者,即使它发送的是请求消息。我们可以这样想:消费者向提供者发送消息,然后消费响应。幸运的是,具有此含义的术语的使用仅限于远程过程调用场景。发送或接收消息的应用程序可以称为消息系统的客户端;更具体的术语是端点或消息端点。 Now it gets confusing. When dealing with Web services, the application that sends a message to the service provider is often referred to as the consumer of the service even though it sends the request message. We can think of it in such a way that the consumer sends a message to the provider and then consumes the response. Luckily, use of the term with this meaning is limited to Remote Procedure Invocation scenarios. An application that sends or receives messages may be called a client of the messaging system; a more specific term is endpoint or message endpoint. |
当开发人员第一次开始使用消息系统时,经常会愚弄他们的是创建通道到底需要做什么。开发人员可以编写调用JMS API 中定义的createQueue方法的 Java 代码或包含new MessageQueue 语句的 .NET 代码,但这代码实际上都不会在消息传递系统中分配新的队列资源。相反,这些代码片段只是实例化一个运行时对象,该对象提供对已使用其管理工具在消息传递系统中创建的资源的访问。
Something that often fools developers when they first get started with using a messaging system is what exactly needs to be done to create a channel. A developer can write Java code that calls the method createQueue defined in the JMS API or .NET code that includes the statement new MessageQueue, but neither code actually allocates a new queue resource in the messaging system. Rather, these pieces of code simply instantiate a runtime object that provides access to a resource that was already created in the messaging system using its administration tools.
在设计消息传递系统的通道时,您应该记住另一个问题:通道很便宜,但它们不是免费的。应用程序需要多个通道来传输不同类型的信息以及将相同的信息传输到许多其他应用程序。每个通道都需要内存来表示消息;持久通道也需要磁盘空间。即使企业系统具有无限的内存和磁盘空间,任何消息传递系统实施通常都会对其可以一致服务的通道数量施加一些硬性或实际限制。因此,在您的应用程序需要时计划创建新通道,但如果需要数千个通道或需要以可能需要数千个通道的方式进行扩展,
There is another issue you should keep in mind when designing the channels for a messaging system: Channels are cheap, but they're not free. Applications need multiple channels for transmitting different types of information and transmitting the same information to lots of other applications. Each channel requires memory to represent the messages; persistent channels require disk space as well. Even if an enterprise system had unlimited memory and disk space, any messaging system implementation usually imposes some hard or practical limit to how many channels it can service consistently. So plan on creating new channels as your application needs them, but if it needs thousands of channels or needs to scale in ways that may require thousands of channels, you'll need to choose a highly scalable messaging system implementation and test that scalability to make sure it meets your needs.
频道名称Channel Names如果通道是逻辑地址,那么这些地址是什么样的?与许多情况一样,详细的答案取决于消息传递系统的实现。然而,在大多数情况下,频道是通过字母数字名称引用的,例如MyChannel 。许多邮件系统支持分层通道命名方案,这使您能够以类似于具有文件夹和子文件夹的文件系统的方式组织通道。例如,MyCorp/Prod/OrderProcessing/NewOrders将指示在MyCorp 的生产应用程序中使用并包含新订单的通道。 If channels are logical addresses, what do these addresses look like? As in so many cases, the detailed answer depends on the implementation of the messaging system. Nevertheless, in most cases channels are referenced by an alphanumeric name, such as MyChannel. Many messaging systems support a hierarchical channel-naming scheme, which enables you to organize channels in a way that is similar to a file system with folders and subfolders. For example, MyCorp/Prod/OrderProcessing/NewOrders would indicate a channel that is used in a production application at MyCorp and contains new orders. |
有两种不同类型的消息通道:点对点通道和发布订阅通道。在同一通道上混合不同的数据类型可能会导致很多混乱;为了避免这种情况,请使用单独的数据类型通道。选择性消费者使一个物理通道在逻辑上像多个通道一样运作。使用消息传递的应用程序通常受益于无效消息的特殊通道,即Invalid Message Channel 。希望使用消息传递但无权访问消息传递客户端的应用程序仍然可以使用通道适配器连接到消息传递系统。一组精心设计的通道形成了消息总线,其作用类似于整组应用程序的消息传递 API。
There are two different kinds of message channels: Point-to-Point Channels and Publish-Subscribe Channels. Mixing different data types on the same channel can cause a lot of confusion; to avoid this, use separate Datatype Channels. Selective Consumer makes one physical channel act logically like multiple channels. Applications that use messaging often benefit from a special channel for invalid messages, an Invalid Message Channel. Applications that wish to use Messaging but do not have access to a messaging client can still connect to the messaging system using Channel Adapters. A well-designed set of channels forms a Message Bus that acts like a messaging API for a whole group of applications.
|
示例: 股票交易 Example: Stock Trading 当股票交易应用程序进行交易时,它会将请求发送到交易请求的消息通道上。处理交易请求的另一个应用程序将寻找它可以在同一消息通道上处理的请求。如果请求应用程序需要请求股票报价,它可能会使用不同的消息通道(专为股票报价设计的消息通道),以便报价请求与交易请求保持分离。 When a stock trading application makes a trade, it puts the request on a Message Channel for trade requests. Another application that processes trade requests will look for those it can process on that same message channel. If the requesting application needs to request a stock quote, it will probably use a different Message Channel, one designed for stock quotes, so that the quote requests stay separate from the trade requests. |
|
示例: J2EE JMS 参考实现 Example: J2EE JMS Reference Implementation 让我们看看如何在 JMS 中创建消息通道。J2EE SDK 附带了 J2EE 服务的参考实现,包括 JMS。可以使用j2ee命令启动参考服务器。必须使用j2eeadmin工具配置消息通道。该工具可以配置队列和主题。 Let's look at how to create a Message Channel in JMS. The J2EE SDK ships with a reference implementation of the J2EE services, including JMS. The reference server can be started with the j2ee command. Message channels have to be configured using the j2eeadmin tool. This tool can configure both queues and topics. j2eeadmin -addJmsDestination jms/mytopic 主题 j2eeadmin -addJmsDestination jms/myqueue 队列 j2eeadmin -addJmsDestination jms/mytopic topic j2eeadmin -addJmsDestination jms/myqueue queue 管理(创建)通道后,JMS 客户端代码就可以访问它们。 Once the channels have been administered (created), they can be accessed by JMS client code. 上下文 jndiContext = new InitialContext();
队列 myQueue = (Queue) jndiContext.lookup("jms/myqueue");
主题 myTopic = (主题) jndiContext.lookup("jms/mytopic");
Context jndiContext = new InitialContext();
Queue myQueue = (Queue) jndiContext.lookup("jms/myqueue");
Topic myTopic = (Topic) jndiContext.lookup("jms/mytopic");
JNDI 查找不会创建队列(或主题);它已经由j2eeadmin命令创建。JNDI 查找只是在 Java 中创建一个Queue实例,该实例对消息传递系统中的队列结构进行建模并提供对队列结构的访问。 The JNDI lookup doesn't create the queue (or topic); it was already created by the j2eeadmin command. The JNDI lookup simply creates a Queue instance in Java that models and provides access to the queue structure in the messaging system. |
|
示例: IBM WebSphere MQ Example: IBM WebSphere MQ 如果您的消息传递系统实现是 IBM 的 WebSphere MQ for Java(它实现了 JMS),那么您将使用 WebSphere MQ JMS 管理工具来创建目标。这将创建一个名为myQueue的队列。 If your messaging system implementation is IBM's WebSphere MQ for Java, which implements JMS, you'll use the WebSphere MQ JMS administration tool to create destinations. This will create a queue named myQueue. 定义 Q(myQueue) DEFINE Q(myQueue) 一旦该队列存在于 WebSphere MQ 中,应用程序就可以访问该队列。 Once that queue exists in WebSphere MQ, an application can access the queue. WebSphere MQ 没有完整的 WebSphere Application Server,因此不包含 JNDI 实现,因此我们无法像在 J2EE 示例中那样使用 JNDI 来查找队列。相反,我们必须通过 JMS 会话访问队列,如下所示。 WebSphere MQ, without the full WebSphere Application Server, does not include a JNDI implementation, so we cannot use JNDI to look up the queue as we did in the J2EE example. Rather, we must access the queue via a JMS session, like this. Session session = // 创建会话
队列队列 = session.createQueue("myQueue");
Session session = // create the session
Queue queue = session.createQueue("myQueue");
|
|
示例: 微软 MSMQ Example: Microsoft MSMQ MSMQ 提供了多种不同的方法来创建消息通道(称为队列)。您可以使用 Microsoft Message Queue Explorer 或计算机管理控制台创建队列(见图)。从这里您可以设置队列属性或删除队列。 MSMQ provides a number of different ways to create a message channel, called a queue. You can create a queue using the Microsoft Message Queue Explorer or the Computer Management console (see figure). From here you can set queue properties or delete queues. 或者,您可以使用代码创建队列。 Alternatively, you can create the queue using code. 使用系统消息传递;
...
MessageQueue.Create("MyQueue");
using System.Messaging;
...
MessageQueue.Create("MyQueue");
创建队列后,应用程序可以通过创建MessageQueue实例并传递队列名称来访问它。 Once the queue is created, an application can access it by creating a MessageQueue instance, passing the name of the queue. MessageQueue mq = new MessageQueue("MyQueue");
MessageQueue mq = new MessageQueue("MyQueue");
|
企业有两个独立的应用程序,它们通过消息传递进行通信,并使用连接它们的消息通道。
An enterprise has two separate applications that are communicating via Messaging, using a Message Channel that connects them.
|
通过消息通道连接的两个应用程序如何交换一条信息? How can two applications connected by a Message Channel exchange a piece of information? |
消息通道通常可以被视为管道,即从一个应用程序到另一个应用程序的管道。那么,数据可以像水一样倒入一端,然后从另一端流出,这可能是合情合理的。但大多数应用程序数据并不是一个连续的流;而是一种连续的数据流。它由记录、对象、数据库行等单元组成。因此通道必须传输数据单元。
A Message Channel can often be thought of as a pipe, a conduit from one application to another. It might stand to reason then that data could be poured into one end, like water, and it would come flowing out of the other end. But most application data isn't one continuous stream; it consists of units, such as records, objects, database rows, and the like. So a channel must transmit units of data.
“传输”数据是什么意思?在函数调用中,调用者可以通过传递指向内存中数据地址的指针来按引用传递参数;这是有效的,因为调用者和函数共享相同的内存堆。类似地,同一进程中的两个线程可以通过传递指针来传递记录或对象,因为它们共享相同的内存空间。
What does it mean to "transmit" data? In a function call, the caller can pass a parameter by reference by passing a pointer to the data's address in memory; this works because both the caller and the function share the same memory heap. Similarly, two threads in the same process can pass a record or object by passing a pointer, since they both share the same memory space.
传递一段数据的两个独立进程还有更多工作要做。由于它们各自有自己的内存空间,因此它们必须将数据从一个内存空间复制到另一个内存空间。数据通常以字节流的形式传输,这是最基本的数据形式。这意味着第一个进程必须将数据编组为字节形式,然后将其从第一个进程复制到第二个进程;第二个进程会将数据解组回其原始形式,以便第二个进程在第一个进程中拥有原始数据的副本。封送处理是远程过程调用 (RPC) 如何将参数发送到远程进程以及进程如何返回结果的方式。
Two separate processes passing a piece of data have more work to do. Since they each have their own memory space, they have to copy the data from one memory space to the other. The data is usually transmitted as a byte stream, the most basic form of data. This means that the first process must marshal the data into byte form, and then copy it from the first process to the second one; the second process will unmarshal the data back into its original form, such that the second process then has a copy of the original data in the first process. Marshaling is how a Remote Procedure Call (RPC) sends arguments to the remote process and how the process returns the result.
因此,消息传递传输离散的数据单元,它通过对来自发送方的数据进行编组并在接收方中对其进行解组来实现这一点,以便接收方拥有自己的本地副本。有用的是一种包装数据单元的简单方法,以便适合在消息传递通道上传输数据。
So messaging transmits discrete units of data, and it does so by marshaling the data from the sender and unmarshaling it in the receiver so that the receiver has its own local copy. What would be helpful would be a simple way to wrap a unit of data such that it is appropriate to transmit the data on a messaging channel.
|
将信息打包成Message ,这是消息传递系统可以通过消息通道传输的数据记录。 Package the information into a Message, a data record that the messaging system can transmit through a Message Channel. |
因此,要通过消息传递系统传输的任何数据都必须转换为可以通过消息传递通道发送的一个或多个消息。
Thus, any data that is to be transmitted via a messaging system must be converted into one or more messages that can be sent through messaging channels.
消息由两个基本部分组成。
A message consists of two basic parts.
报头 消息传递系统使用的信息,描述正在传输的数据、其来源、目的地等。
Header Information used by the messaging system that describes the data being transmitted, its origin, its destination, and so on.
正文 正在传输的数据,通常会被消息传递系统忽略并简单地按原样传输。
Body The data being transmitted, which is generally ignored by the messaging system and simply transmitted as is.
这个概念并不是消息传递所独有的。邮政服务邮件和电子邮件都以离散邮件消息的形式发送数据。以太网以数据包的形式传输数据,TCP/IP 的 IP 部分(例如互联网)也是如此。互联网上的流媒体实际上是一系列的数据包。
This concept is not unique to messaging. Both postal service mail and e-mail send data as discrete mail messages. An Ethernet network transmits data as packets, as does the IP part of TCP/IP such as the Internet. Streaming media on the Internet is actually a series of packets.
对于消息传递系统来说,所有消息都是相同的:如标头所描述的要传输的某些数据体。然而,对于应用程序程序员来说,存在不同类型的消息,即不同的应用程序使用风格。使用命令消息调用另一个应用程序中的过程。使用文档消息将一组数据传递到另一个应用程序。使用事件消息通知另一个应用程序此应用程序中的更改。如果其他应用程序应发回回复,请使用Request-Reply。
To the messaging system, all messages are the same: some body of data to be transmitted as described by the header. However, to the applications programmer, there are different types of messagesthat is, different application styles of use. Use a Command Message to invoke a procedure in another application. Use a Document Message to pass a set of data to another application. Use an Event Message to notify another application of a change in this application. If the other application should send back a reply, use Request-Reply.
如果应用程序希望发送的信息多于一条消息所能容纳的信息,请将数据分成更小的部分并将这些部分作为消息序列发送。如果数据仅在有限的时间内有用,请将此使用时间指定为Message Expiration 。由于消息的所有不同发送者和接收者必须就消息中的数据格式达成一致,因此将格式指定为规范数据模型。
If an application wishes to send more information than one message can hold, break the data into smaller parts and send the parts as a Message Sequence. If the data is only useful for a limited amount of time, specify this use-by time as a Message Expiration. Since all the various senders and receivers of messages must agree on the format of the data in the messages, specify the format as a Canonical Data Model.
|
示例: JMS 消息 Example: JMS Message 在 JMS 中,消息由Message 类型表示,该类型有多个子类型。每个子类型中,头结构都是相同的;这是因类型而异的正文格式。 In JMS, a message is represented by the type Message, which has several subtypes. In each subtype, the header structure is the same; it's the body format that varies by type.
|
|
示例: .NET 消息 Example: .NET Message 在.NET 中,Message类实现消息类型。它有一个属性Body ,其中包含作为Object的消息内容;BodyStream 将内容存储为Stream 。另一个属性BodyType指定正文包含的数据类型,例如字符串、日期、货币、数字或任何其他对象。 In .NET, the Message class implements the message type. It has a property, Body, which contains the contents of the message as an Object; BodyStream stores the contents as a Stream. Another property, BodyType, specifies the type of data the body contains, such as a string, a date, a currency, a number, or any other object. |
|
示例: SOAP 消息 Example: SOAP Message 在 SOAP 协议 [ SOAP 1.1 ] 中,SOAP 消息是Message 的一个示例。SOAP 消息是一个 XML 文档,它是一个信封(根SOAP-ENV:Envelope元素),其中包含可选标头(SOAP -ENV:Header元素)和必需的正文(SOAP -ENV:Body元素)。这个XML文档是一个可以传输的原子数据记录(通常传输协议是HTTP),因此它是一条消息。 In the SOAP protocol [SOAP 1.1], a SOAP message is an example of Message. A SOAP message is an XML document that is an envelope (a root SOAP-ENV:Envelope element) that contains an optional header (a SOAP-ENV:Header element) and required body (a SOAP-ENV:Body element). This XML document is an atomic data record that can be transmitted (typically the transmission protocol is HTTP) so it is a message. 下面是 SOAP 规范中的 SOAP 消息示例,其中显示了包含标头和正文的信封。 Here is an example of a SOAP message from the SOAP spec that shows an envelope containing a header and a body. <SOAP-ENV:信封 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap /encoding/"/> <SOAP-ENV:Header> <t:Transaction xmlns:t="some-URI" SOAP-ENV:mustUnderstand="1"> 5 </t:Transaction> </SOAP-ENV:Header> <SOAP-ENV:Body> <m:GetLastTradePrice xmlns:m="Some-URI"> <symbol>DEF</symbol> </m:GetLastTradePrice> </SOAP-ENV:Body> </SOAP-ENV:Envelope> SOAP 还演示了消息的递归性质,因为 SOAP 消息可以通过消息传递系统进行传输,这意味着消息传递系统消息对象(例如,JMS中的 javax.jms.Message类型或 JMS 中的System.Messaging.Message类型的对象) .NET)包含 SOAP 消息(XML SOAP-ENV:Envelope文档)。在这种情况下,传输协议不是 HTTP,而是消息传递系统的内部协议(消息系统可能会使用 HTTP 或其他网络协议来传输数据,但消息传递系统使传输可靠)。有关跨不同消息传递系统传输消息的更多信息,请参阅信封包装器。 SOAP also demonstrates the recursive nature of messages, because a SOAP message can be transmitted via a messaging system, which means that a messaging system message object (e.g., an object of type javax.jms.Message in JMS or System.Messaging.Message in .NET) contains the SOAP message (the XML SOAP-ENV:Envelope document). In this scenario, the transport protocol isn't HTTP but the messaging system's internal protocol (which in turn may be using HTTP or some other network protocol to transmit the data, but the messaging system makes the transmission reliable). For more information on transporting a message across a different messaging system, see Envelope Wrapper. |
在许多企业集成场景中,单个事件会触发一系列处理步骤,每个处理步骤执行特定的功能。例如,假设一个新订单以消息的形式到达我们的企业。一项要求可能是对消息进行加密,以防止窃听者刺探客户的订单。第二个要求是消息包含数字证书形式的身份验证信息,以确保订单仅由受信任的客户下达。此外,外部各方可能会发送重复的消息(还记得流行购物网站上仅单击“立即订购”按钮一次的所有警告吗?)。为了避免重复发货和顾客不满意,我们需要在启动后续订单处理步骤之前消除重复的消息。为了满足这些要求,我们需要将一系列可能重复的、包含额外身份验证数据的加密消息转换为一系列唯一的、简单的纯文本订单消息,不含无关的数据字段。
In many enterprise integration scenarios, a single event triggers a sequence of processing steps, each performing a specific function. For example, let's assume a new order arrives in our enterprise in the form of a message. One requirement may be that the message is encrypted to prevent eavesdroppers from spying on a customer's order. A second requirement is that the messages contain authentication information in the form of a digital certificate to ensure that orders are placed only by trusted customers. In addition, duplicate messages could be sent from external parties (remember all the warnings on the popular shopping sites to click the Order Now button only once?). To avoid duplicate shipments and unhappy customers, we need to eliminate duplicate messages before subsequent order processing steps are initiated. To meet these requirements, we need to transform a series of possibly duplicated, encrypted messages containing extra authentication data into a series of unique, simple plain-text order messages without the extraneous data fields.
|
如何对消息进行复杂的处理,同时又保持独立性和灵活性? How can we perform complex processing on a message while maintaining independence and flexibility? |
一种可能的解决方案是编写一个全面的“传入消息消息处理模块”来执行所有必要的功能。然而,这种方法不灵活且难以测试。如果我们需要添加或删除步骤怎么办?例如,如果大客户可以在专用网络上下订单并且不需要加密怎么办?
One possible solution would be to write a comprehensive "incoming message massaging module" that performs all the necessary functions. However, such an approach would be inflexible and difficult to test. What if we need to add a step or remove one? For example, what if orders can be placed by large customers who are on a private network and do not require encryption?
在单个组件内实现所有功能也减少了重用的机会。创建更小、定义明确的组件使我们能够在其他流程中重用它们。例如,订单状态消息可以被加密,但不需要进行重复数据删除,因为重复的状态请求通常是无害的。将解密函数分离到一个单独的模块中允许我们将该函数重用于其他消息。
Implementing all functions inside a single component also reduces opportunities for reuse. Creating smaller, well-defined components allows us to reuse them in other processes. For example, order status messages may be encrypted but do not need to be de-duped because duplicate status requests are generally not harmful. Separating the decryption function into a separate module allows us to reuse this function for other messages.
集成解决方案通常连接一组异构系统。因此,不同的处理步骤可能需要在不同的物理机器上执行,例如当各个处理步骤只能在特定的系统上执行时。例如,出于安全原因,解密传入消息所需的私钥可能仅在指定机器上可用,并且无法从任何其他机器访问。这意味着解密组件必须在该指定机器上执行,而其他步骤可以在其他机器上执行。同样,不同的处理步骤可以使用不同的编程语言或技术来实现,从而防止它们在同一进程内运行,甚至在同一台计算机上运行。
Integration solutions typically connect a collection of heterogeneous systems. As a result, different processing steps may need to execute on different physical machines, such as when individual processing steps can only execute on specific systems. For example, it is possible that the private key required to decrypt incoming messages is only available on a designated machine and cannot be accessed from any other machine for security reasons. This means that the decryption component has to execute on this designated machine, whereas the other steps may execute on other machines. Likewise, different processing steps may be implemented using different programming languages or technologies that prevent them from running inside the same process or even on the same computer.
在单独的组件中实现每个功能仍然会引入组件之间的依赖关系。例如,如果解密组件用解密结果调用认证组件,那么如果没有认证函数,我们就无法使用解密函数。如果我们能够将现有组件“组合”为一系列处理步骤,使得每个组件独立于系统中的其他组件,那么我们就可以解决这些依赖性。这意味着组件公开通用外部接口,以便它们可以互换。
Implementing each function in a separate component can still introduce dependencies between components. For example, if the decryption component calls the authentication component with the results of the decryption, we cannot use the decryption function without the authentication function. We could resolve these dependencies if we could "compose" existing components into a sequence of processing steps in such a way that each component is independent from the other components in the system. This would imply that components expose generic external interfaces so that they are interchangeable.
如果我们使用异步消息传递,我们应该利用从一个组件向另一个组件发送消息的异步方面。例如,一个组件可以将消息发送到另一个组件以进行进一步处理,而无需等待结果。使用这种技术,我们可以并行处理多个消息,每个组件内处理一个消息。
If we use asynchronous messaging, we should take advantage of the asynchronous aspects of sending messages from one component to another. For example, a component can send a message to another component for further processing without waiting for the results. Using this technique, we could process multiple messages in parallel, one inside each component.
|
使用管道和过滤器架构风格将较大的处理任务划分为一系列较小的、独立的处理步骤(过滤器),这些步骤通过通道(管道)连接。 Use the Pipes and Filters architectural style to divide a larger processing task into a sequence of smaller, independent processing steps (filters) that are connected by channels (pipes). |
每个过滤器都公开一个非常简单的接口:它接收入站管道上的消息,处理消息,并将结果发布到出站管道。管道将一个过滤器连接到下一个过滤器,将输出消息从一个过滤器发送到下一个过滤器。由于所有组件都使用相同的外部接口,因此可以通过将组件连接到不同的管道来将它们组合成不同的解决方案。我们可以添加新的过滤器、省略现有的过滤器或将它们重新排列成新的序列,而无需更改过滤器本身。过滤器和管道之间的连接有时称为端口。在基本形式中,每个滤波器组件都有一个输入端口和一个输出端口。
Each filter exposes a very simple interface: It receives messages on the inbound pipe, processes the message, and publishes the results to the outbound pipe. The pipe connects one filter to the next, sending output messages from one filter to the next. Because all components use the same external interface, they can be composed into different solutions by connecting the components to different pipes. We can add new filters, omit existing ones, or rearrange them into a new sequenceall without having to change the filters themselves. The connection between filter and pipe is sometimes called a port. In the basic form, each filter component has one input port and one output port.
当应用于我们的示例问题时,管道和过滤器架构会产生由两个管道连接的三个过滤器(见图)。我们需要一根额外的管道将消息发送到解密组件,还需要一根管道将明文订单消息从重复数据删除程序发送到订单管理系统。这总共有四个管道。
When applied to our example problem, the Pipes and Filters architecture results in three filters connected by two pipes (see figure). We need one additional pipe to send messages to the decryption component and one to send the clear-text order messages from the de-duper to the order management system. This makes a total of four pipes.
管道和过滤器描述了消息传递系统的基本架构风格:各个处理步骤(过滤器)通过消息传递通道(管道)链接在一起。本节和后续部分中的许多模式(例如路由和转换模式)都基于此管道和过滤器架构风格。这使您可以轻松地将单个模式组合成更大的解决方案。
Pipes and Filters describes a fundamental architectural style for messaging systems: Individual processing steps (filters) are chained together through the messaging channels (pipes). Many patterns in this and the following sections, such as routing and transformation patterns, are based on this Pipes and Filters architectural style. This lets you easily combine individual patterns into larger solutions.
管道和过滤器样式使用抽象管道来解耦组件。管道允许一个组件将消息发送到管道中,以便稍后可以由该组件未知的另一个进程使用该消息。这种管道的明显实现是消息通道。通常,消息通道在过滤器之间提供语言、平台和位置独立性。这使我们能够出于依赖性、维护或性能原因灵活地将处理步骤转移到不同的机器。然而,消息通道如果所有组件实际上都可以驻留在同一台机器上,那么消息传递基础设施提供的功能可能会相当重量级。使用简单的内存队列来实现管道会更加高效。因此,设计组件以便它们与抽象管道接口进行通信是很有用的。然后可以将该接口的实现换出以使用消息通道或替代实现(例如内存队列)。消息传递网关描述了如何设计组件以实现这种灵活性。
The Pipes and Filters style uses abstract pipes to decouple components from each other. The pipe allows one component to send a message into the pipe so that it can be consumed later by another process that is unknown to the component. The obvious implementation for such a pipe is a Message Channel. Typically, a Message Channel provides language, platform, and location independence between the filters. This affords us the flexibility to move a processing step to a different machine for dependency, maintenance, or performance reasons. However, a Message Channel provided by a messaging infrastructure can be quite heavyweight if all components can in fact reside on the same machine. Using a simple in-memory queue to implement the pipes would be much more efficient. Therefore, it is useful to design the components so that they communicate with an abstract pipe interface. The implementation of that interface can then be swapped out to use a Message Channel or an alternative implementation such as an in-memory queue. The Messaging Gateway describes how to design components for this flexibility.
管道和过滤器架构的潜在缺点之一是所需通道的数量较多。首先,通道可能不是无限的资源,因为通道提供缓冲和其他消耗内存和 CPU 周期的功能。此外,将消息发布到通道会涉及一定量的开销,因为数据必须从应用程序内部格式转换为消息传递基础结构自己的格式。在接收端,这个过程必须相反。如果我们使用长链过滤器,我们将付出灵活性的代价,但由于重复的消息数据转换,性能可能会降低。
One of the potential downsides of a Pipes and Filters architecture is the larger number of required channels. First, channels may not be an unlimited resource, since channels provide buffering and other functions that consume memory and CPU cycles. Also, publishing a message to a channel involves a certain amount of overhead because the data has to be translated from the application-internal format into the messaging infrastructure's own format. At the receiving end, this process has to be reversed. If we are using a long chain of filters, we are paying for the gain in flexibility with potentially lower performance due to repeated message data conversion.
管道和过滤器的纯粹形式允许每个过滤器只有一个输入端口和一个输出端口。在处理消息传递时,我们可以稍微放宽这个属性。组件可以从多个通道消费消息,也可以将消息输出到多个通道(例如,消息路由器)。同样,多个过滤器组件可以使用单个消息通道的消息。点对点通道确保只有一个过滤器组件使用每条消息。
The pure form of Pipes and Filters allows each filter to have only a single input port and a single output port. When dealing with Messaging, we can relax this property somewhat. A component may consume messages off more than one channel and also output messages to more than one channel (for example, a Message Router). Likewise, multiple filter components can consume messages off a single Message Channel. A Point-to-Point Channel ensures that only one filter component consumes each message.
使用管道和过滤器还可以提高可测试性,这是一个经常被忽视的好处。我们可以通过将测试消息传递给组件并将结果消息与预期结果进行比较来测试每个单独的处理步骤。单独测试和调试每个核心功能会更有效,因为我们可以针对特定功能定制测试机制。例如,为了测试加密/解密功能,我们可以传入大量包含随机数据的消息。在加密和解密每条消息后,我们将其与原始消息进行比较。另一方面,为了测试身份验证,我们需要提供带有与系统中已知用户匹配的特定身份验证代码的消息。
Using Pipes and Filters also improves testability, an often overlooked benefit. We can test each individual processing step by passing a Test Message to the component and comparing the result message to the expected outcome. It is more efficient to test and debug each core function in isolation because we can tailor the test mechanism to the specific function. For example, to test the encryption/decryption function we can pass in a large number of messages containing random data. After we encrypt and decrypt each message we compare it with the original. On the other hand, to test authentication, we need to supply messages with specific authentication codes that match known users in the system.
使用异步消息通道连接组件允许链中的每个单元在自己的线程或自己的进程中操作。当一个单元完成一条消息的处理后,它可以将该消息发送到输出通道并立即开始处理另一条消息。它不必等待后续组件读取并处理消息。这允许多个消息在通过各个阶段时同时进行处理。例如,在第一条消息被解密之后,它可以被传递到认证组件。与此同时,下一条消息已经可以被解密(见图)。我们将这样的配置称为处理管道因为消息流过过滤器就像液体流过管道一样。与严格的顺序处理相比,处理管道可以显着提高系统吞吐量。
Connecting components with asynchronous Message Channels allows each unit in the chain to operate in its own thread or its own process. When a unit has completed processing one message, it can send the message to the output channel and immediately start processing another message. It does not have to wait for the subsequent components to read and process the message. This allows multiple messages to be processed concurrently as they pass through the individual stages. For example, after the first message has been decrypted, it can be passed on to the authentication component. At the same time, the next message can already be decrypted (see figure). We call such a configuration a processing pipeline because messages flow through the filters like liquid flows through a pipe. When compared to strictly sequential processing, a processing pipeline can significantly increase system throughput.
使用管道和过滤器进行管道处理
Pipeline Processing with Pipes and Filters
然而,整体系统吞吐量受到链中最慢进程的限制。我们可以部署该流程的多个并行实例以提高吞吐量。在这种情况下,需要一个具有竞争消费者的点对点通道来保证通道上的每条消息恰好由 N 个可用处理器之一使用。这使我们能够加快最耗时的流程并提高整体吞吐量。但我们需要注意,此配置可能会导致消息处理无序。如果消息的顺序很关键,我们只能运行每个组件的一个实例,或者必须使用 Resequencer 。
However, the overall system throughput is limited by the slowest process in the chain. We can deploy multiple parallel instances of that process to improve throughput. In this scenario, a Point-to-Point Channel with Competing Consumers is needed to guarantee that each message on the channel is consumed by exactly one of N available processors. This allows us to speed up the most time-intensive process and improve overall throughput. We need to be aware, though, that this configuration can cause messages to be processed out of order. If the sequence of messages is critical, we can run only one instance of each component or we must use a Resequencer.
通过并行处理提高吞吐量
Increasing Throughput with Parallel Processing
例如,如果我们假设解密消息比验证消息慢得多,则可以使用图中所示的配置,运行解密组件的三个并行实例。如果每个过滤器都是无状态的,那么并行过滤器效果最佳,也就是说,它在处理消息后返回到之前的状态。这意味着我们无法轻松运行多个并行的重复数据删除组件,因为该组件维护其已接收的所有消息的历史记录,因此不是无状态的。
For example, if we assume that decrypting a message is much slower than authenticating it, we can use the configuration shown in the figure, running three parallel instances of the decryption component. Parallelizing filters works best if each filter is statelessthat is, it returns to the previous state after a message has been processed. This means that we cannot easily run multiple parallel de-dupe components because the component maintains a history of all messages that it already received and is therefore not stateless.
管道和过滤器架构绝不是一个新概念。这种架构的简单优雅与灵活性和高吞吐量相结合,使我们很容易理解管道和过滤器架构的流行。简单的语义还允许使用形式化方法来描述架构。
Pipes and Filters architectures are by no means a new concept. The simple elegance of this architecture combined with the flexibility and high throughput makes it easy to understand the popularity of Pipes and Filters architectures. The simple semantics also allow formal methods to be used to describe the architecture.
[ Kahn ] 在 1974 年将 Kahn 过程网络描述为一组通过无限 FIFO(先进先出)通道连接的并行过程。[ Garlan ] 包含关于不同架构风格的好章节,包括管道和过滤器。[ Monroe ]详细论述了架构风格和设计模式之间的关系。[ PLoPD1 ] 包含 Regine Meunier 的“管道和过滤器架构”,它构成了 [ POSA ]中包含的管道和过滤器模式的基础。管道和过滤器的几乎所有与集成相关的实现遵循 [ POSA ]中提出的“场景 IV” ,使用有源过滤器独立地从队列管道拉取、处理和推送。[ POSA ]描述的模式假设每个元素在从一个过滤器传递到另一个过滤器时经历相同的处理步骤。在集成场景中通常不会出现这种情况。在许多情况下,消息是根据消息内容或外部控制动态路由的。事实上,路由在企业集成中非常常见,以至于它有自己的模式,即消息路由器。
[Kahn] described Kahn Process Networks in 1974 as a set of parallel processes that are connected by unbounded FIFO (First-In, First-Out) channels. [Garlan] contains a good chapter on different architectural styles, including Pipes and Filters. [Monroe] gives a detailed treatment of the relationships between architectural styles and design patterns. [PLoPD1] contains Regine Meunier's "The Pipes and Filters Architecture," which formed the basis for the Pipes and Filters pattern included in [POSA]. Almost all integration-related implementations of Pipes and Filters follow the "Scenario IV" presented in [POSA], using active filters that pull, process, and push independently from and to queuing pipes. The pattern described by [POSA] assumes that each element undergoes the same processing steps as it is passed from filter to filter. This is generally not the case in an integration scenario. In many instances, messages are routed dynamically based on message content or external control. In fact, routing is such a common occurrence in enterprise integration that it warrants its own patterns, the Message Router.
词汇Vocabulary在讨论管道和过滤器架构时,我们需要谨慎对待术语过滤器。我们稍后定义两个附加模式:消息过滤器和内容过滤器。虽然这两种都是通用过滤器的特殊情况,但这种模式语言中的许多其他模式也是如此。换句话说,模式不必涉及过滤功能(例如,消除字段或消息)才能成为管道和过滤器意义上的过滤器。我们可以通过重命名管道和过滤器架构风格来避免这种混乱。然而,我们认为管道和过滤器这是一个如此重要且被广泛讨论的概念,如果我们给它一个新名称,就会更加令人困惑。我们试图在这些模式中谨慎地使用“过滤器”这个词,并试图弄清楚我们是在谈论管道和过滤器中的通用过滤器,还是过滤消息的消息过滤器/内容过滤器。如果我们认为可能仍然存在混淆,我们将通用过滤器称为组件,这是一个足够通用(并且经常被滥用)的术语,它不应该给我们带来任何麻烦。 When discussing Pipes and Filters architectures, we need to be cautious with the term filter. We later define two additional patterns, the Message Filter and the Content Filter. While both of these are special cases of a generic filter, so are many other patterns in this pattern language. In other words, a pattern does not have to involve a filtering function (e.g., eliminating fields or messages) in order to be a filter in the sense of Pipes and Filters. We could have avoided this confusion by renaming the Pipes and Filters architectural style. However, we felt that Pipes and Filters is such an important and widely discussed concept that it would be even more confusing if we gave it a new name. We are trying to use the word filter cautiously throughout these patterns and trying to be clear about whether we are talking about a generic filter as in Pipes and Filters or a Message Filter /Content Filter that filters messages. If we thought there might still be confusion, we called the generic filter a component, which is a generic enough (and often abused enough) term that it should not get us into any trouble. |
管道和过滤器与通信顺序进程 (CSP) 的概念有一些相似之处。由 Hoare 于 1978 年提出 [ CSP],CSP 提供了一个简单的模型来描述并行处理系统中发生的同步问题。CSP 的基本机制是通过输入输出 (I/O) 同步两个进程。当进程 A 指示它已准备好向进程 B 输出,并且进程 B 声明它已准备好从进程 A 输入时,I/O 就会发生。如果其中一个发生而另一个不为真,则该进程将处于等待状态排队直到其他进程准备好。CSP 与集成解决方案的不同之处在于,它们不是松散耦合的,“管道”也不提供任何排队机制。尽管如此,我们还是可以从学术界对 CSP 的广泛研究中受益。
Pipes and Filters share some similarities with the concept of Communicating Sequential Processes (CSPs). Introduced by Hoare in 1978 [CSP], CSPs provide a simple model to describe synchronization problems that occur in parallel processing systems. The basic mechanism underlying CSPs is the synchronization of two processes via input-output (I/O). I/O occurs when process A indicates that it is ready to output to process B, and process B states that it is ready to input from process A. If one of these happens without the other being true, the process is put on a wait queue until the other process is ready. CSPs are different from integration solutions in that they are not as loosely coupled, nor do the "pipes" provide any queuing mechanisms. Nevertheless, we can benefit from the extensive treatment of CSPs in the academic world.
|
示例: C# 和 MSMQ 中的简单过滤器 Example: Simple Filter in C# and MSMQ 以下代码片段显示了具有一个输入端口和一个输出端口的过滤器的通用基类。基本实现只是打印接收到的消息的正文并将其发送到输出端口。更有趣的过滤器将继承Processor类并重写ProcessMessage 方法以对消息执行其他操作,即转换消息内容或将其路由到不同的输出通道。 The following code snippet shows a generic base class for a filter with one input port and one output port. The base implementation simply prints the body of the received message and sends it to the output port. A more interesting filter would subclass the Processor class and override the ProcessMessage method to perform additional actions on the messagethat is, transform the message content or route it to different output channels. 您注意到处理器在实例化期间接收对输入和输出通道的引用。因此,该类既不依赖于特定通道,也不依赖于任何其他过滤器。这允许我们实例化多个过滤器并以任意配置将它们链接在一起。 You notice that the Processor receives references to an input and output channel during instantiation. Thus, the class is tied to neither specific channels nor any other filter. This allows us to instantiate multiple filters and to chain them together in arbitrary configurations. 使用系统; 使用系统消息传递; 命名空间 PipesAndFilters { 公共类处理器 { 受保护的消息队列输入队列; 受保护的消息队列输出队列; 公共处理器(消息队列输入队列、消息队列 using System; using System.Messaging; namespace PipesAndFilters { public class Processor { protected MessageQueue inputQueue; protected MessageQueue outputQueue; public Processor (MessageQueue inputQueue, MessageQueue outputQueue) { this.inputQueue = inputQueue; this.outputQueue = outputQueue; } public void Process() { inputQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnReceiveCompleted); inputQueue.BeginReceive(); } private void OnReceiveCompleted(Object source, ReceiveCompletedEventArgs asyncResult) { MessageQueue mq = (MessageQueue)source; Message inputMessage = mq.EndReceive(asyncResult .AsyncResult); inputMessage.Formatter = new XmlMessageFormatter (new String[] {"System .String,mscorlib"}); Message outputMessage = ProcessMessage(inputMessage); outputQueue.Send(outputMessage); mq.BeginReceive(); } protected virtual Message ProcessMessage(Message m) { Console.WriteLine("Received Message: " + m.Body); return (m); } } } 这个实现是一个事件驱动的消费者。Process方法注册传入消息,并指示消息传递系统在每次消息到达时调用 OnReceiveCompleted 方法。此方法从传入事件对象中提取消息数据并调用虚拟方法ProcessMessage 。 This implementation is an Event-Driven Consumer. The Process method registers for incoming messages and instructs the messaging system to invoke the method OnReceiveCompleted every time a message arrives. This method extracts the message data from the incoming event object and calls the virtual method ProcessMessage. 这个简单的过滤器示例不是事务性的。如果在处理消息时(在将其发送到输出通道之前)发生错误,则该消息将丢失。这在生产环境中通常是不可取的。请参阅事务客户端以获取此问题的解决方案。 This simple filter example is not transactional. If an error occurs while processing the message (before it is sent to the output channel), the message is lost. This is generally not desirable in a production environment. See Transactional Client for a solution to this problem. |
Multiple processing steps in a Pipes and Filters chain are connected by Message Channels.
|
如何解耦各个处理步骤,以便消息可以根据一组条件传递到不同的过滤器? How can you decouple individual processing steps so that messages can be passed to different filters depending on a set of conditions? |
管道和过滤器架构风格通过固定管道将过滤器直接相互连接。这是有道理的,因为管道和过滤器模式(例如,[ POSA ] )的许多应用程序都基于大量数据项,每个数据项都经历相同的顺序处理步骤。例如,编译器总是首先执行词法分析,其次执行语法分析,最后执行语义分析。另一方面,基于消息的集成解决方案处理不一定与单个较大数据集关联的单独消息。因此,各个消息更有可能需要一系列不同的处理步骤。
The Pipes and Filters architectural style connects filters directly to each other with fixed pipes. This makes sense because many applications of the Pipes and Filters pattern (e.g., [POSA]) are based on a large set of data items, each of which undergoes the same sequential processing steps. For example, a compiler will always execute the lexical analysis first, the syntactic analysis second, and the semantic analysis last. Message-based integration solutions, on the other hand, deal with individual messages that are not necessarily associated with a single, larger data set. As a result, individual messages are more likely to require a different series of processing steps.
消息通道将消息的发送者和接收者解耦。 这也意味着多个应用程序可以将消息发布到消息通道。因此,消息通道可以包含来自不同源的消息,这些消息可能必须根据消息的类型或其他标准进行不同的处理。您可以为每种消息类型创建一个单独的消息通道(稍后将作为数据类型通道更详细地解释这一概念))并将每个通道连接到该消息类型所需的处理步骤。然而,这将要求消息发起者了解不同处理步骤的选择标准,以便将消息发布到正确的通道。它还可能导致消息通道数量的爆炸。 此外,消息经历哪些步骤的决定可能不仅仅取决于消息的来源。例如,我们可以想象这样一种情况:消息的目的地根据迄今为止通过通道的消息数量而变化。没有任何一个发起者知道这个号码,因此无法将消息发送到正确的通道。
A Message Channel decouples the sender and the receiver of a Message. This also means that multiple applications can publish Messages to a Message Channel. As a result, a Message Channel can contain messages from different sources that may have to be treated differently based on the type of the message or other criteria. You could create a separate Message Channel for each message type (a concept explained in more detail later as a Datatype Channel) and connect each channel to the required processing steps for that message type. However, this would require the message originators to be aware of the selection criteria for different processing steps in order to publish the message to the correct channel. It could also lead to an explosion of the number of Message Channels. Furthermore, the decision on which steps the message undergoes may not just depend on the origin of the message. For example, we could imagine a situation where the destination of a message varies depending on the number of messages that have passed through the channel so far. No single originator would know this number and would therefore be unable to send the message to the correct channel.
消息通道提供了应用程序将消息发布到消息不进一步了解该消息的目的地。因此,消息的路径可以根据订阅消息通道的组件而改变。然而,这种类型的“路由”没有考虑各个消息的属性。一旦组件订阅了消息通道,默认情况下它将消耗来自该通道的所有消息,无论单个消息的特定属性如何。此行为类似于在 UNIX 中使用管道符号来处理文本文件。它允许您将进程组成管道和过滤器链,但在链的生命周期内,所有文本行都会经历相同的步骤。
Message Channels provide a very basic form of routing capabilities. An application publishes a Message to a Message Channel and has no further knowledge of that Message's destination. Therefore, the path of the Message can change depending on which component subscribes to the Message Channel. However, this type of "routing" does not take into account the properties of individual messages. Once a component subscribes to a Message Channel, it will by default consume all messages from that channel regardless of the individual message's specific properties. This behavior is similar to the use of the pipe symbol in UNIX to process text files. It allows you to compose processes into a Pipes and Filters chain, but for the lifetime of the chain, all lines of text undergo the same steps.
我们可以让接收组件本身负责确定是否应该处理到达公共消息通道的消息。但这是有问题的,因为一旦消息被使用并且组件确定它不需要该消息,它就不能只是将消息放回到通道上以供另一个组件检出。一些消息系统允许接收者在不从通道中删除消息的情况下检查消息属性,以便决定是否使用该消息。然而,这不是通用的解决方案,并且还将使用组件与特定类型的消息联系起来,因为消息选择的逻辑现在直接内置在组件中。这将降低该组件重用的可能性,并消除作为管道和过滤器模型的关键优势的可组合性。
We could make the receiving component itself responsible for determining whether it should process a message that arrives on a common Message Channel. This is problematic, though, because once the message is consumed and the component determines that it does not want the message, it can't just put the message back on the channel for another component to check out. Some messaging systems allow receivers to inspect message properties without removing the message from the channel so that it can decide whether to consume the message. However, this is not a general solution and also ties the consuming component to a specific type of message because the logic for message selection is now built right into the component. This would reduce the potential for reuse of that component and eliminate the composability that is the key strength of the Pipes and Filters model.
许多这些替代方案都假设我们可以修改参与的组件来满足我们的需求。然而,在大多数集成解决方案中,构建块(组件)是大型应用程序,在大多数情况下根本无法修改,例如,因为它们是打包应用程序或遗留应用程序。这使得根据消息传递系统或其他应用程序的需要来调整消息生成或消费应用程序是不经济的,甚至是不可能的。
Many of these alternatives assume that we can modify the participating components to meet our needs. In most integration solutions, however, the building blocks (components) are large applications that in most cases cannot be modified at allfor example, because they are packaged applications or legacy applications. This makes it uneconomical or even impossible to adjust the message-producing or -consuming applications to the needs of the messaging system or other applications.
管道和过滤器的优点之一是各个组件的可组合性。此属性使我们能够在过滤器链中插入额外的步骤,而无需更改现有组件。这提供了通过在两个过滤器之间插入另一个过滤器来解耦两个过滤器的选项,该过滤器确定下一步要执行的步骤。
One advantage of Pipes and Filters is the composability of the individual components. This property enables us to insert additional steps into the filter chain without having to change existing components. This opens up the option of decoupling two filters by inserting between them another filter that determines what step to execute next.
|
插入一个特殊的过滤器,即消息路由器,它使用一个消息通道中的消息并将其重新发布到不同的消息通道,具体取决于一组条件。 Insert a special filter, a Message Router, which consumes a Message from one Message Channel and republishes it to a different Message Channel, depending on a set of conditions. |
消息路由器与管道和过滤器的基本概念不同,它连接到多个输出通道(即,它有多个输出端口)。然而,由于管道和过滤器架构,消息路由器周围的组件完全不知道消息路由器的存在。他们只是从一个渠道消费消息并将其发布到另一个渠道。消息路由器的一个定义属性是它不会修改消息内容;它只关心消息的目的地。
The Message Router differs from the basic notion of Pipes and Filters in that it connects to multiple output channels (i.e., it has more than one output port). However, thanks to the Pipes and Filters architecture, the components surrounding the Message Router are completely unaware of the existence of a Message Router. They simply consume messages off one channel and publish them to another. A defining property of the Message Router is that it does not modify the message contents; it concerns itself only with the destination of the message.
使用消息路由器的主要好处是消息目的地的决策标准保存在单个位置。如果定义了新的消息类型,添加了新的处理组件,或者路由规则发生了变化,我们只需要更改消息路由器逻辑,而所有其他组件不受影响。此外,由于所有消息都通过单个消息路由器,因此保证传入的消息按照正确的顺序一一处理。
The key benefit of using a Message Router is that the decision criteria for the destination of a message are maintained in a single location. If new message types are defined, new processing components are added, or routing rules change, we need to change only the Message Router logic, while all other components remain unaffected. Also, since all messages pass through a single Message Router, incoming messages are guaranteed to be processed one by one in the correct order.
虽然消息路由器的目的是使过滤器彼此解耦,但使用消息路由器实际上可能会导致相反的效果。消息路由器组件必须了解所有可能的目标通道,以便将消息发送到正确的通道。如果可能的目的地列表频繁更改,消息路由器可能会成为维护瓶颈。在这些情况下,最好让各个收件人决定他们对哪些消息感兴趣。您可以通过使用发布-订阅通道和消息过滤器数组来实现此目的。我们通过称为预测路由和反应式过滤来对比这两种替代方案(有关更详细的比较,请参阅第 7消息章路由”中的消息过滤器)。
While the intent of a Message Router is to decouple filters from each other, using a Message Router can actually cause the opposite effect. The Message Router component must have knowledge of all possible destination channels in order to send the message to the correct channel. If the list of possible destinations changes frequently, the Message Router can turn into a maintenance bottleneck. In those cases, it would be better to let the individual recipients decide which messages they are interested in. You can accomplish this by using a Publish-Subscribe Channel and an array of Message Filters. We contrast these two alternatives by calling them predictive routing and reactive filtering (for a more detailed comparison, see the Message Filter in Chapter 7, "Message Routing").
由于消息路由器需要插入额外的处理步骤,因此可能会降低性能。许多基于消息的系统必须先对来自一个通道的消息进行解码,然后才能将其放置到另一通道上,如果消息本身没有真正改变,这会导致计算开销。这种开销可能会将消息路由器变成性能瓶颈。通过并行使用多个路由器或添加额外的硬件,可以最大限度地减少这种影响。因此,消息吞吐量(每时间单位处理的消息数量)可能不会受到影响,但延迟(一条消息通过系统的时间)几乎肯定会增加。
Because a Message Router requires the insertion of an additional processing step, it can degrade performance. Many message-based systems have to decode the message from one channel before it can be placed on another channel, which causes computational overhead if the message itself does not really change. This overhead can turn a Message Router into a performance bottleneck. By using multiple routers in parallel or adding additional hardware, this effect can be minimized. As a result, the message throughput (number of messages processed per time unit) may not be impacted, but the latency (time for one message to travel through the system) will almost certainly increase.
与大多数优秀工具一样,消息路由器也可能被滥用。故意使用消息路由器可能会将松散耦合的优点变成缺点。松散耦合的系统可能会导致难以理解解决方案的“大局”(即通过系统的整体消息流)。这是消息传递解决方案的常见问题,而使用路由器可能会加剧该问题。如果一切都与其他一切松散耦合,那么就不可能理解消息实际流向的方向。这会使测试、调试和维护变得复杂。许多工具可以帮助缓解这个问题。首先,我们可以使用消息历史记录在运行时检查消息并查看它们遍历了哪些组件。或者,我们可以编译系统中每个组件订阅或发布的所有通道的列表。有了这些知识,我们就可以绘制跨组件的所有可能消息流的图表。许多 EAI 包在中央存储库中维护频道订阅信息,使这种类型的静态分析变得更加容易。
Like most good tools, Message Routers can also be abused. Deliberate use of Message Routers can turn the advantage of loose coupling into a disadvantage. Loosely coupled systems can make it difficult to understand the "big picture" of the solutionthe overall flow of messages through the system. This is a common problem with messaging solutions, and the use of routers can exacerbate the problem. If everything is loosely coupled to everything else, it becomes impossible to understand in which direction messages actually flow. This can complicate testing, debugging, and maintenance. A number of tools can help alleviate this problem. First, we can use the Message History to inspect messages at runtime and see which components they traversed. Alternatively, we can compile a list of all channels to which each component in the system subscribes or publishes. With this knowledge we can draw a graph of all possible message flows across components. Many EAI packages maintain channel subscription information in a central repository, making this type of static analysis easier.
消息路由器可以使用任意数量的标准来确定传入消息的输出通道。最简单的情况是固定路由器。在这种情况下,仅定义单个输入通道和单个输出通道。固定路由器从输入通道消耗一条消息并将其发布到输出通道。为什么我们要使用这样一个无脑的路由器?固定路由器可能有助于有意解耦子系统,以便我们稍后可以插入更智能的路由器。或者,我们可能在多个集成解决方案之间中继消息。在大多数情况下,固定路由器将与消息转换器或通道适配器结合使用转换消息内容或通过不同的通道类型发送消息。
A Message Router can use any number of criteria to determine the output channel for an incoming message. The most trivial case is a fixed router. In this case, only a single input channel and a single output channel are defined. The fixed router consumes one message off the input channel and publishes it to the output channel. Why would we ever use such a brainless router? A fixed router may be useful to intentionally decouple subsystems so that we can insert a more intelligent router later. Or, we may be relaying messages between multiple integration solutions. In most cases, a fixed router will be combined with a Message Translator or a Channel Adapter to transform the message content or send the message over a different channel type.
许多消息路由器仅根据消息本身的属性来决定消息目的地,例如消息类型或特定消息字段的值。我们将这样的路由器称为基于内容的路由器。这种类型的路由器非常常见,因此基于内容的路由器模式对其进行了更详细的描述。
Many Message Routers decide the message destination only on properties of the message itselffor example, the message type or the values of specific message fields. We call such a router a Content-Based Router. This type of router is so common that the Content-Based Router pattern describes it in more detail.
其他消息路由器根据环境条件决定消息的目的地。我们将这些路由器称为基于上下文的路由器。此类路由器通常用于执行负载平衡、测试或故障转移功能。例如,如果处理组件发生故障,基于上下文的路由器可以将消息重新路由到另一个处理组件,从而提供故障转移能力。其他路由器将消息流均匀地分配到多个通道,以实现类似于负载均衡器的并行处理。一些消息通道已经提供了基本的负载平衡功能,而无需使用消息路由器,因为多个竞争消费者每个人都可以尽可能快地从同一通道消费消息。但是,消息路由器可以具有额外的内置智能来路由消息,而不是通道实现的简单循环。
Other Message Routers decide the message's destination based on environment conditions. We call these routers context-based routers. Such routers are commonly used to perform load-balancing, test, or failover functionality. For example, if a processing component fails, the context-based router can reroute messages to another processing component and thus provide failover capability. Other routers split the flow of messages evenly across multiple channels to achieve parallel processing similar to a load balancer. Some Message Channels already provide basic load-balancing capabilities without the use of a Message Router because multiple Competing Consumers can each consume messages off the same channel as fast as they can. However, a Message Router can have additional built-in intelligence to route the messages as opposed to a simple round-robin implemented by the channel.
换句话说,许多消息路由器是无状态的,它们一次只查看一条消息来做出路由决策。其他路由器在做出路由决策时会考虑先前消息的内容。例如,管道和过滤器示例使用的路由器通过保留已接收到的所有消息的列表来消除重复消息。这些路由器是有状态的。
Many Message Routers are statelessin other words, they look at only one message at a time to make the routing decision. Other routers take the content of previous messages into account when making a routing decision. For example, the Pipes and Filters example used a router that eliminates duplicate messages by keeping a list of all messages it already received. These routers are stateful.
大多数消息路由器包含用于路由决策的硬编码逻辑。但是,某些变体连接到控制总线,以便中间件解决方案可以更改决策标准,而无需进行任何代码更改或中断消息流。例如,控制总线可以将全局变量的值传播到系统中的所有消息路由器。这对于允许消息系统从测试模式切换到生产模式的测试非常有用。动态路由器根据来自每个潜在接收者的控制消息动态地配置自身。
Most Message Routers contain hard-coded logic for the routing decision. However, some variants connect to a Control Bus so that the middleware solution can change the decision criteria without having to make any code changes or interrupting the flow of messages. For example, the Control Bus can propagate the value of a global variable to all Message Routers in the system. This can be very useful for testing to allow the messaging system to switch from test to production mode. The Dynamic Router configures itself dynamically based on control messages from each potential recipient.
第 7 章“消息路由”介绍了消息路由器的更多变体。
Chapter 7, "Message Routing," introduces more variants of the Message Router.
|
示例: 商业 EAI 工具 Example: Commercial EAI Tools 消息路由器的概念是消息代理概念的核心,几乎在所有商业 EAI 工具中都实现了这一概念。这些工具接受传入消息,验证它们,转换它们,并将它们路由到正确的目的地。这种架构使参与的应用程序不必完全了解其他应用程序,因为Message Broker应用程序之间的经纪人。这是企业集成中的一个关键功能,因为大多数要连接的应用程序都是打包的或遗留的应用程序,并且集成必须以非侵入方式进行,即不更改应用程序代码。因此,中间件必须合并所有路由逻辑,因此应用程序不必这样做。Message Broker是 [ GoF ] 中提供的中介器的集成等价物。 The notion of a Message Router is central to the concept of a Message Broker, implemented in virtually all commercial EAI tools. These tools accept incoming messages, validate them, transform them, and route them to the correct destination. This architecture alleviates the participating applications from having to be aware of other applications altogether because the Message Broker brokers between the applications. This is a key function in enterprise integration because most applications to be connected are packaged or legacy applications and the integration has to happen nonintrusivelythat is, without changing the application code. Therefore, the middleware has to incorporate all routing logic so the applications do not have to. The Message Broker is the integration equivalent of a Mediator presented in [GoF]. |
|
示例: 使用 C# 和 MSMQ 的简单路由器 Example: Simple Router with C# and MSMQ 此代码示例演示了一个非常简单的路由器,它根据简单的条件将传入消息路由到两个可能的输出通道之一。 This code example demonstrates a very simple router that routes an incoming message to one of two possible output channels based on a simple condition. 简单路由器类 { 受保护的消息队列inQueue; 受保护的消息队列outQueue1; 受保护的消息队列outQueue2; 公共 SimpleRouter(消息队列 inQueue, 消息队列 class SimpleRouter { protected MessageQueue inQueue; protected MessageQueue outQueue1; protected MessageQueue outQueue2; public SimpleRouter(MessageQueue inQueue, MessageQueue outQueue1, MessageQueue outQueue2) { this.inQueue = inQueue; this.outQueue1 = outQueue1; this.outQueue2 = outQueue2; inQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnMessage); inQueue.BeginReceive(); } private void OnMessage(Object source, ReceiveCompletedEventArgs asyncResult) { MessageQueue mq = (MessageQueue)source; Message message = mq.EndReceive(asyncResult.AsyncResult); if (IsConditionFulfilled()) outQueue1.Send(message); else outQueue2.Send(message); mq.BeginReceive(); } protected bool toggle = false; protected bool IsConditionFulfilled () { toggle = !toggle; return toggle; } } 代码相对简单。与Pipes 和 Filters中提供的简单过滤器一样, SimpleRouter类使用 C# 委托实现事件驱动的消息使用者。构造函数将方法OnMessage注册为到达inQueue 的消息的处理程序。这会导致 .NET Framework 为到达inQueue调用OnMessage方法。OnMessage通过调用IsConditionFulfilled 方法确定将消息路由到何处。在这个简单的例子中,IsConditionFulfilled只需在两个通道之间切换,将消息序列均匀地分配到outQueue1和outQueue2 之间。为了使代码保持最少,这个简单的路由器不是事务性的,也就是说,如果路由器在消耗来自输入通道的消息之后并将其发布到输出通道之前崩溃,则该消息将会丢失。事务性客户端解释了如何使端点事务化。 The code is relatively straightforward. Like the simple filter presented in Pipes and Filters, the SimpleRouter class implements an Event-Driven Consumer of messages using C# delegates. The constructor registers the method OnMessage as the handler for messages arriving on the inQueue. This causes the .NET Framework to invoke the method OnMessage for every message that arrives on the inQueue. OnMessage figures out where to route the message by calling the method IsConditionFulfilled. In this trivial example, IsConditionFulfilled simply toggles between the two channels, dividing the sequence of messages evenly between outQueue1 and outQueue2. In order to keep the code to a minimum, this simple router is not transactionalthat is, if the router crashes after it consumes a message from the input channel and before it publishes it to the output channel, the message would be lost. Transactional Client explains how to make endpoints transactional. |
前面的模式展示了如何构造消息以及如何将它们路由到正确的目的地。在许多情况下,企业集成解决方案在现有应用程序(例如遗留系统、打包应用程序、自行开发的自定义应用程序或外部合作伙伴运营的应用程序)之间路由消息。这些应用程序中的每一个通常都是围绕专有数据模型构建的。每个应用程序对客户实体、定义客户的属性以及与客户相关的其他实体的概念可能略有不同。例如,会计系统可能对客户的纳税人 ID 号更感兴趣,而客户关系管理 (CRM) 系统则存储电话号码和地址。应用程序的底层数据模型通常驱动物理数据库模式、接口文件格式或应用程序编程接口 (API) 的设计,这些实体是集成解决方案必须与之交互的实体。因此,每个应用程序通常期望接收模仿应用程序内部数据格式的消息。
The previous patterns show how to construct messages and how to route them to the correct destination. In many cases, enterprise integration solutions route messages between existing applications such as legacy systems, packaged applications, homegrown custom applications, or applications operated by external partners. Each of these applications is usually built around a proprietary data model. Each application may have a slightly different notion of the Customer entity, the attributes that define a Customer, and other entities to which a Customer is related. For example, the accounting system may be more interested in the customer's taxpayer ID numbers, whereas the customer-relationship management (CRM) system stores phone numbers and addresses. The application's underlying data model usually drives the design of the physical database schema, an interface file format, or an application programming interface (API)those entities with which an integration solution must interface. As a result, each application typically expects to receive messages that mimic the application's internal data format.
除了各种应用程序中包含的专有数据模型和数据格式之外,集成解决方案通常还通过独立于特定应用程序的标准化数据格式与外部业务合作伙伴进行交互。许多联盟和标准机构定义了这些协议;例如,RosettaNet、ebXML、OAGIS 和许多其他特定行业联盟。在许多情况下,集成解决方案需要能够使用“官方”数据格式与外部各方进行通信,即使内部系统基于专有格式。
In addition to the proprietary data models and data formats incorporated in the various applications, integration solutions often interact with external business partners via standardized data formats that are independent from specific applications. A number of consortia and standards bodies define these protocols; for example, RosettaNet, ebXML, OAGIS, and many other industry-specific consortia. In many cases, the integration solution needs to be able to communicate with external parties using the "official" data formats, even though the internal systems are based on proprietary formats.
|
使用不同数据格式的系统如何通过消息传递相互通信? How can systems using different data formats communicate with each other using messaging? |
如果我们可以修改所有应用程序以使用通用数据格式,我们就可以避免转换消息。由于多种原因,这变得很困难(请参阅共享数据库)。首先,更改应用程序的数据格式是有风险的、困难的,并且需要对固有业务功能进行大量更改。对于大多数遗留应用程序来说,更改数据格式在经济上根本不可行。我们可能都记得与 Y2K 改造相关的工作,其中更改的范围仅限于单个字段的大小!
We could avoid having to transform messages if we could modify all applications to use a common data format. This turns out to be difficult for a number of reasons (see Shared Database). First, changing an application's data format is risky, difficult, and requires a lot of changes to inherent business functionality. For most legacy applications, data format changes are simply not economically feasible. We may all remember the effort related to the Y2K retrofits, where the scope of the change was limited to the size of a single field!
此外,虽然我们可能会让多个应用程序使用相同的数据字段名称甚至相同的数据类型,但物理表示可能仍然有很大不同。一个应用程序可能使用 XML 文档,而另一个应用程序则使用 COBOL copybook。
Also, while we may get multiple applications to use the same data field names and maybe even the same data types, the physical representation may still be quite different. One application may use XML documents, whereas the other application uses COBOL copybooks.
此外,如果我们调整一个应用程序的数据格式以匹配另一应用程序的数据格式,我们就会将这两个应用程序彼此更紧密地联系在一起。企业集成的关键架构原则之一是应用程序之间的松耦合(请参阅规范数据模型)。修改一个应用程序以匹配另一个应用程序的数据格式将违反这一原则,因为它使两个应用程序直接依赖于彼此的内部表示。这消除了替换或更改一个应用程序而不影响另一个应用程序的可能性,这种情况在企业集成中相当常见。
Furthermore, if we adjust the data format of one application to match that of another application, we are tying the two applications more tightly to each other. One of the key architectural principles in enterprise integration is loose coupling between applications (see Canonical Data Model). Modifying one application to match another application's data format would violate this principle because it makes two applications directly dependent on each other's internal representation. This eliminates the possibility of replacing or changing one application without affecting the other application, a scenario that is fairly common in enterprise integration.
我们可以将数据格式转换直接合并到消息端点中。这样,所有应用程序都将以通用格式而不是应用程序的内部数据格式发布和使用消息。然而,这种方法需要访问端点代码,而打包应用程序通常不是这种情况。此外,将格式转换硬编码到端点将减少代码重用的机会。
We could incorporate the data format translation directly into the Message Endpoint. This way, all applications would publish and consume messages in a common format as opposed to in the application's internal data format. However, this approach requires access to the endpoint code, which is usually not the case for packaged applications. In addition, hard-coding the format translation to the endpoint would reduce the opportunities for code reuse.
|
在其他过滤器或应用程序之间使用特殊的过滤器(消息转换器)将一种数据格式转换为另一种数据格式。 Use a special filter, a Message Translator, between other filters or applications to translate one data format into another. |
消息转换器是[GoF] 中描述的适配器模式的消息传递等效项。 适配器将组件的接口转换为另一个接口,以便它可以在不同的上下文中使用。
The Message Translator is the messaging equivalent of the Adapter pattern described in [GoF]. An adapter converts the interface of a component into another interface so it can be used in a different context.
消息翻译可能需要在许多不同的级别上进行。例如,数据元素可以共享相同的名称和数据类型,但可以以不同的表示形式使用(例如,XML 文件与逗号分隔值与固定长度字段)。或者,所有数据元素都可以以 XML 格式表示,但使用不同的标签名称。为了总结不同类型的翻译,我们可以将其分为多个层(大致借用 OSI 参考模型)。
Message translation may need to occur at a number of different levels. For example, data elements may share the same name and data types but may be used in different representations (e.g., XML file vs. comma-separated values vs. fixed-length fields). Or, all data elements may be represented in XML format but use different tag names. To summarize the different kinds of translation, we can divide it into multiple layers (loosely borrowing from the OSI Reference Model).
层 Layer | 处理 Deals With | 转型需求(示例) Transformation Needs (Example) | 工具/技术 Tools/Techniques |
|---|---|---|---|
数据结构(应用层) Data Structures (Application Layer) | 实体、关联、基数 Entities, associations, cardinality | 将多对多关系压缩为聚合。 Condense many-to-many relationship into aggregation. | 结构映射模式、自定义代码 Structural mapping patterns, custom code |
数据类型 Data Types | 字段名称、数据类型、值域、约束、代码值 Field names, data types, value domains, constraints, code values | 将邮政编码从数字转换为字符串。将名字和姓氏字段连接到单个名称字段。将美国州名替换为两个字符的代码。 Convert ZIP code from numeric to string. Concatenate First Name and Last Name fields to single Name field. Replace U.S. state name with two-character code. | EAI 可视化转换编辑器、XSL、数据库查找、自定义代码 EAI visual transformation editors, XSL, database lookups, custom code |
数据表示 Data Representation | 数据格式(XML、名称-值对、固定长度数据字段、EAI 供应商格式等) Data formats (XML, name-value pairs, fixed-length data fields, EAI vendor formats, etc.) 字符集(ASCII、UniCode、EBCDIC) Character sets (ASCII, UniCode, EBCDIC) 加密/压缩 Encryption/compression | 解析数据表示并以不同的格式呈现。 Parse data representation and render in a different format. 根据需要解密/加密。 Decrypt/encrypt as necessary. | XML 解析器、EAI 解析器/渲染器工具、自定义 API XML parsers, EAI parser/renderer tools, custom APIs |
运输 Transport | 通信协议:TCP/IP 套接字、HTTP、SOAP、JMS、TIBCO RendezVous Communications protocols: TCP/IP sockets, HTTP, SOAP, JMS, TIBCO RendezVous | 跨协议移动数据而不影响消息内容。 Move data across protocols without affecting message content. | 通道适配器, EAI适配器 Channel Adapter, EAI adapters |
“堆栈”底部的传输层提供不同系统之间的数据传输。它负责跨不同网段的完整可靠的数据传输,并处理丢失的数据包和其他网络错误。一些EAI 供应商提供自己的传输协议(例如TIBCO RendezVous),而其他集成技术则利用TCP/IP 协议(例如SOAP)。不同传输层之间的转换可以由通道适配器模式提供。
The Transport layer at the bottom of the "stack" provides data transfer between the different systems. It is responsible for complete and reliable data transfer across different network segments and deals with lost data packets and other network errors. Some EAI vendors provide their own transport protocols (e.g., TIBCO RendezVous), whereas other integration technologies leverage TCP/IP protocols (e.g., SOAP). Translation between different transport layers can be provided by the Channel Adapter pattern.
数据表示层也称为语法层。该层定义了传输的数据的表示。这种转换是必要的,因为传输层通常只传输字符或字节流。这意味着复杂的数据结构必须转换为字符串。此转换的常见格式包括 XML、固定长度字段(例如 EDI 记录)和专有格式。在许多情况下,数据也被压缩或加密并带有校验位或数字证书。为了与具有不同数据表示形式的系统进行接口,可能必须对数据进行解密、解压缩和解析,然后必须呈现新的数据格式,并且还可能进行压缩和加密。
The Data Representation layer is also referred to as the syntax layer. This layer defines the representation of data that is transported. This translation is necessary because the transport layer typically transports only character or byte streams. This means that complex data structures have to be converted into a character string. Common formats for this conversion include XML, fixed-length fields (e.g., EDI records), and proprietary formats. In many cases, data is also compressed or encrypted and carries check digits or digital certificates. In order to interface systems with different data representations, data may have to be decrypted, uncompressed, and parsed, and then the new data format must be rendered and possibly compressed and encrypted as well.
数据类型层定义应用程序(域)模型所基于的应用程序数据类型。在这里,我们处理诸如日期字段是否表示为字符串或本机日期结构、日期是否带有时间组件、它们基于哪个时区等决策。我们还可以考虑邮政编码字段是否仅表示美国邮政编码或可以包含加拿大邮政编码。如果是美国邮政编码,是否包含 ZIP+4?是强制性的吗?它存储在一个字段中还是两个字段中?其中许多问题通常在所谓的数据字典中得到解决。与数据类型相关的问题超出了字段是字符串类型还是整数类型的范围。考虑按地区组织的销售数据。一个部门使用的应用程序可以将国家分为四个区域:西部、中部、南部和东部,用字母 W、C、S 和 E 标识。另一个部门可以区分太平洋地区和山区,并区分太平洋地区和山区。从东南向东北。每个区域都由一个两位数的数字来标识。字母E对应什么数字?
The Data Types layer defines the application data types on which the application (domain) model is based. Here we deal with such decisions as whether date fields are represented as strings or as native date structures, whether dates carry a time-of-day component, which time zone they are based on, and so on. We may also consider whether the field Postal Code denotes only a U.S. ZIP code or can contain Canadian postal codes. In the case of a U.S. zip code, do we include a ZIP+4? Is it mandatory? Is it stored in one field, or two? Many of these questions are usually addressed in so-called Data Dictionaries. The issues related to data types go beyond whether a field is of type string or integer. Consider sales data that is organized by region. The application used by one department may divide the country into four regions: West, Central, South, and East, identified by the letters W, C, S, and E. Another department may differentiate the Pacific region from the mountain region and distinguish the Northeast from the Southeast. Each region is identified by a two-digit number. What number does the letter E correspond to?
数据结构层描述应用程序域模型级别的数据。因此也称为应用层。该层定义应用程序处理的逻辑实体,例如客户、地址或帐户。它还定义了这些实体之间的关系:一个客户可以拥有多个帐户吗?一个客户可以有多个地址吗?客户可以分享地址吗?多个客户可以共用一个账户吗?地址是帐户还是客户的一部分?这是实体关系图和类图的领域。
The Data Structures layer describes the data at the level of the application domain model. It is therefore also referred to as the application layer. This layer defines the logical entities that the application deals with, such as customer, address, or account. It also defines the relationships between these entities: Can one customer have multiple accounts? Can a customer have multiple addresses? Can customers share an address? Can multiple customers share an account? Is the address part of the account or the customer? This is the domain of entity-relationship diagrams and class diagrams.
集成中的许多设计权衡都是由解耦组件或应用程序的需要驱动的。解耦是实现变革管理的重要工具。集成通常连接现有应用程序,并且必须适应这些应用程序的更改。消息通道使应用程序不必知道彼此的位置。消息路由器甚至可以使应用程序不必就公共消息通道达成一致。然而,如果应用程序仍然依赖于彼此的数据格式,这种形式的解耦只能在应用程序之间实现有限的独立性。消息翻译器可以消除这种额外的依赖性。
Many of the design trade-offs in integration are driven by the need to decouple components or applications. Decoupling is an essential tool to enable the management of change. Integration typically connects existing applications and has to accommodate changes to these applications. Message Channels decouple applications from having to know each other's location. A Message Router can even decouple applications from having to agree on a common Message Channel. However, this form of decoupling achieves only limited independence between applications if they still depend on each other's data formats. A Message Translator can remove this additional level of dependency.
许多业务场景需要不止一层的转换。例如,假设表示为固定格式文件的 EDI 850 采购订单记录必须转换为通过 HTTP 发送到订单管理系统的 XML 文档,该系统使用不同的 Order 对象定义。所需的转换跨越所有四个级别:传输从文件传输更改为HTTP,数据格式从固定字段格式更改为XML,并且数据类型和数据格式都必须转换以符合Order由订单管理系统定义的对象。分层模型的优点在于,您可以处理一层而不必担心较低层,因此可以一次专注于一个抽象级别(请参见下图)。
Many business scenarios require transformations at more than one layer. For example, let's assume an EDI 850 Purchase Order record represented as a fixed-format file has to be translated to an XML document sent over HTTP to the order management system, which uses a different definition of the Order object. The required transformation spans all four levels: The transport changes from file transfer to HTTP, the data format changes from a fixed-field format to XML, and both data types and data formats have to be converted to comply with the Order object defined by the order management system. The beauty of a layered model is that you can treat one layer without worrying about the lower layers and therefore can focus on one level of abstraction at a time (see the following figure).
跨多个层的映射
Mapping Across Multiple Layers
使用管道和过滤器链接多个消息转换器单元会产生以下架构(请参见下一页的图)。为每一层创建一个消息转换器允许我们在其他场景中重用这些组件。例如,通道适配器和EDI 到 XML消息转换器可以以通用方式实现,以便它们可以重新用于任何传入的 EDI 文档。
Chaining multiple Message Translator units using Pipes and Filters results in the following architecture (see figure on the next page). Creating one Message Translator for each layer allows us to reuse these components in other scenarios. For example, the Channel Adapter and the EDI-to-XML Message Translator can be implemented in a generic fashion so that they can be reused for any incoming EDI document.
链接多个 消息转换器
Chaining Multiple Message Translators
链接多个消息转换器还允许您更改单个层使用的转换,而不影响任何其他层。您可以使用相同的结构转换机制,但不是将数据表示形式转换为固定格式,而是可以通过交换数据表示形式转换将其转换为逗号分隔的文件。
Chaining multiple Message Translators also allows you to change the transformations used at an individual layer without affecting any of the other layers. You could use the same structural transformation mechanisms, but instead of converting the data representation into a fixed format, you could convert it into a comma-separated file by swapping out the data representation transformation.
消息翻译器模式有许多专业化和变体。信封包装器将消息数据包装在信封内,以便可以在消息传递系统中传输。内容丰富器增强消息内的信息,而内容过滤器则删除信息。索赔检查删除信息但将其存储以供以后检索。Normalizer可以将多种不同的消息格式转换为一致的格式。最后,规范数据模型展示了如何利用多个消息转换器s 实现数据格式解耦。在每个模式中,都可能发生复杂的结构转换(例如,将多对多关系映射为一对一关系)。
There are many specializations and variations of the Message Translator pattern. An Envelope Wrapper wraps the message data inside an envelope so that it can be transported across a messaging system. A Content Enricher augments the information inside a message, whereas the Content Filter removes information. The Claim Check removes information but stores it for later retrieval. The Normalizer can convert a number of different message formats into a consistent format. Last, the Canonical Data Model shows how to leverage multiple Message Translators to achieve data format decoupling. Inside each of those patterns, complex structural transformations can occur (e.g., mapping a many-to-many relationship into a one-to-one relationship).
|
示例: 使用 XSL 进行结构转换 Example: Structural Transformation with XSL 转换是一种常见的需求,因此 W3C 定义了一种用于 XML 文档转换的标准语言:可扩展样式表语言 (XSL)。XSL 的一部分是 XSL 转换 (XSLT) 语言,这是一种基于规则的语言,可将一个 XML 文档转换为另一种格式。由于这是一本关于集成而不是关于 XSLT 的书,因此我们仅提供一个简单的示例(有关所有详细细节,请参阅规范 [ XSLT 1.0 ],或者通过查看代码示例来学习,请参阅 [ Tennison ] ) 。为了简单起见,我们通过显示示例 XML 文档而不是 XML 模式来解释所需的转换。 Transformation is such a common need that the W3C defined a standard language for the transformation of XML documents: the Extensible Stylesheet Language (XSL). Part of XSL is the XSL Transformation (XSLT) language, a rules-based language that translates one XML document into a different format. Since this is a book on integration and not on XSLT, we just present a simple example (for all the gory details, see the spec [XSLT 1.0], or to learn by reviewing code examples, see [Tennison]). In order to keep things simple, we explain the required transformation by showing example XML documents as opposed to XML schemas. 例如,假设我们有一个传入的 XML 文档,需要将其传递到会计系统。如果两个系统都使用 XML,则数据表示层是相同的,我们需要涵盖字段名称、数据类型和结构方面的任何差异。我们假设传入的文档如下所示。 For example, let's assume we have an incoming XML document and need to pass it to the accounting system. If both systems use XML, the Data Representation layer is identical, and we need to cover any differences in field names, data types, and structure. Let's assume the incoming document looks like this. <数据>
<顾客>
<名字>乔</名字>
<姓氏>母鹿</姓氏>
<地址类型=“主要”>
<参考id =“55355”/>
</地址>
<地址类型=“次要”>
<参考id =“77889”/>
</地址>
</客户>
<地址id="55355">
<街道>123 主要</街道>
<城市>旧金山</城市>
<州>加利福尼亚州</州>
<邮政编码>94123</邮政编码>
<国家>美国</国家>
<手机类型=“手机”>
<区域>415</区域>
<前缀>555</前缀>
<号码>1234</号码>
</电话>
<电话类型=“家庭”>
<区域>415</区域>
<前缀>555</前缀>
<号码>5678</号码>
</电话>
</地址>
<地址id="77889">
<公司>ThoughtWorks</公司>
<街>汤森410号</街>
<城市>旧金山</城市>
<州>加利福尼亚州</州>
<邮政编码>94107</邮政编码>
<国家>美国</国家>
</地址>
</数据>
<data>
<customer>
<firstname>Joe</firstname>
<lastname>Doe</lastname>
<address type="primary">
<ref id="55355"/>
</address>
<address type="secondary">
<ref id="77889"/>
</address>
</customer>
<address id="55355">
<street>123 Main</street>
<city>San Francisco</city>
<state>CA</state>
<postalcode>94123</postalcode>
<country>USA</country>
<phone type="cell">
<area>415</area>
<prefix>555</prefix>
<number>1234</number>
</phone>
<phone type="home">
<area>415</area>
<prefix>555</prefix>
<number>5678</number>
</phone>
</address>
<address id="77889">
<company>ThoughtWorks</company>
<street>410 Townsend</street>
<city>San Francisco</city>
<state>CA</state>
<postalcode>94107</postalcode>
<country>USA</country>
</address>
</data>
该 XML 文档包含客户数据。每个客户可以与多个地址相关联,每个地址可以包含多个电话号码。XML 将地址表示为独立的实体,以便多个客户可以共享一个地址。 This XML document contains customer data. Each customer can be associated with multiple addresses, each of which can contain multiple phone numbers. The XML represents addresses as independent entities so that multiple customers could share an address. 我们假设会计系统需要以下表示。(如果您认为德语标签名称有点牵强,请记住,最受欢迎的企业软件 (SAP) 之一以其德语字段名称而闻名!) Let's assume the accounting system needs the following representation. (If you think that the German tag names are bit farfetched, keep in mind that one of the most popular pieces of enterprise software (SAP) is famous for its German field names!) <昆德>
<姓名>乔·多伊</姓名>
<地址>
<大街>123号大街</大街>
<Ort>旧金山</Ort>
<电话>415-555-1234</电话>
</地址>
</昆德>
<Kunde>
<Name>Joe Doe</Name>
<Adresse>
<Strasse>123 Main</Strasse>
<Ort>San Francisco</Ort>
<Telefon>415-555-1234</Telefon>
</Adresse>
</Kunde>
生成的文档结构更加简单。标签名称不同,并且某些字段合并为单个字段。由于只有一个地址和电话号码,我们需要根据业务规则从原始文档中选择一个。以下 XSLT 程序将原始文档转换为所需的格式。它通过匹配传入文档的元素并将它们转换为所需的文档格式来实现这一点。 The resulting document has a much simpler structure. Tag names are different, and some fields are merged into a single field. Since there is room for only one address and phone number, we need to pick one from the original document based on business rules. The following XSLT program transforms the original document into the desired format. It does so by matching elements of the incoming document and translating them into the desired document format. <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999 /XSL/Transform"> <xsl:output method="xml" indent="yes"/> <xsl:key name="addrlookup" match="/data/address" use="@id"/> <xsl:template match="data"> <xsl:apply-templates select="customer"/> </xsl:template> <xsl:template match="customer"> <Kunde> <Name> <xsl:value-of select="concat(firstname, ' ', lastname)"/> </Name> <Adresse> <xsl:variable name="id" select=". /address[@type='primary']/ref/@id"/> <xsl:call-template name="getaddr"> <xsl:with-param name="addr" select="key ('addrlookup', $id)"/> </xsl:call-template> </Adresse> </Kunde> </xsl:template> <xsl:template name="getaddr"> <xsl:param name="addr"/> <Strasse> <xsl:value-of select="$addr/street"/> </Strasse> <Ort> <xsl:value-of select="$addr/city"/> </Ort> <Telefon> <xsl:choose> <xsl:when test="$addr/phone[@type='cell']"> <xsl:apply-templates select="$addr /phone[@type='cell']" mode="getphone"/> </xsl:when> <xsl:otherwise> <xsl:apply-templates select="$addr /phone[@type='home']" mode="getphone"/> </xsl:otherwise> </xsl:choose> </Telefon> </xsl:template> <xsl:template match="phone" mode="getphone"> <xsl:value-of select="concat(area, '-', prefix, '-', number)"/> </xsl:template> <xsl:template match="*"/> </xsl:stylesheet> XSL 基于模式匹配,如果您像我们大多数人一样习惯过程编程,那么读起来可能有点麻烦。简而言之,每当传入 XML 文档中的元素与match属性中指定的表达式匹配时,就会调用<xsl:template>元素内的指令。例如,该行 XSL is based on pattern matching and can be a bit hairy to read if you are used to procedural programming like most of us. In a nutshell, the instructions inside an <xsl:template> element are called whenever an element in the incoming XML document matches the expression specified in the match attribute. For example, the line <xsl:模板匹配=“客户”> <xsl:template match="customer"> 导致对源文档中的每个<customer>元素执行后续行。接下来的语句连接名字和姓氏并将其输出到<Name>元素内。获取地址有点棘手。XSL 代码查找<address>元素的正确实例并调用子例程getaddr 。getaddr 从原始<address>元素中提取地址和电话号码。如果有的话,它使用手机号码,否则使用家庭电话号码。 causes the subsequent lines to be executed for each <customer> element in the source document. The next statements concatenate first and last name and output it inside the <Name> element. Getting the address is a little trickier. The XSL code looks up the correct instance of the <address> element and calls the subroutine getaddr. getaddr extracts the address and phone number from the original <address> element. It uses the cell phone number if one is present, or the home phone number otherwise. |
|
示例: 视觉转换工具 Example: Visual Transformation Tools 如果您发现 XSL 编程有点神秘,那么您有很好的伙伴。因此,大多数集成供应商提供了可视化转换编辑器,分别在屏幕的左侧和右侧显示两种文档格式的结构。然后,用户可以通过在格式之间绘制连接线来将格式之间的元素关联起来。这比编码 XSL 简单得多。一些供应商(例如 Contivo)完全专注于转换工具。 If you find XSL programming a bit cryptic, you are in good company. Therefore, most integration vendors provide a visual transformation editor that displays the structure of the two document formats on the left-hand side and the right-hand side of the screen respectively. The users can then associate elements between the formats by drawing connecting lines between them. This can be a lot simpler than coding XSL. Some vendors, such as Contivo, specialize entirely in transformation tools. 下图显示了集成到 Visual Studio 中的 Microsoft BizTalk Mapper 编辑器。该图比 XSL 脚本更清楚地显示各个元素之间的映射。另一方面,一些细节(例如,如何选择地址)隐藏在所谓的 functoid 图标下方。 The following figure shows the Microsoft BizTalk Mapper editor that is integrated into Visual Studio. The diagram shows the mapping between individual elements more clearly than the XSL script. On the other hand, some of the details (e.g., how the address is chosen) are hidden underneath the so-called functoid icons. 创建转换:拖放样式 Creating Transformations: The Drag-Drop Style 能够拖放转换大大缩短了开发消息翻译器的学习曲线。但通常情况下,当涉及到调试或需要创建复杂的解决方案时,可视化工具也可能成为一种负担。因此,许多工具允许您在 XSL 和可视化表示之间来回切换。 Being able to drag and drop transformations shortens the learning curve for developing a Message Translator dramatically. As so often though, visual tools can also become a liability when it comes to debugging or when you need to create complex solutions. Therefore, many tools let you switch back and forth between XSL and the visual representation. |
Applications are communicating by sending Messages to each other via Message Channels.
|
应用程序如何连接到消息通道来发送和接收消息? How does an application connect to a messaging channel to send and receive Messages? |
应用程序和消息系统是两套独立的软件。应用程序为某种类型的用户提供功能,而消息传递系统管理用于传输消息以进行通信的消息传递通道。即使消息传递系统被合并为应用程序的基本部分,它仍然是一个独立的、专门的功能提供者,很像数据库管理系统或 Web 服务器。由于应用程序和消息传递系统是分开的,因此它们必须有一种连接和协同工作的方式。
The application and the messaging system are two separate sets of software. The application provides functionally for some type of user, whereas the messaging system manages messaging channels for transmitting messages for communication. Even if the messaging system is incorporated as a fundamental part of the application, it is still a separate, specialized provider of functionality, much like a database management system or a Web server. Because the application and the messaging system are separate, they must have a way to connect and work together.
应用程序与消息通道断开连接
Applications Disconnected from a Message Channel
消息传递系统是一种服务器,能够接受请求并响应它们。就像数据库接受和检索数据一样,消息传递服务器接受和传递消息。消息传递系统是消息传递服务器。
A messaging system is a type of server, capable of taking requests and responding to them. Like a database accepting and retrieving data, a messaging server accepts and delivers messages. A messaging system is a messaging server.
服务器需要客户端,而使用消息传递的应用程序是消息传递服务器的客户端。但应用程序不一定知道如何成为消息传递客户端,就像它们不一定知道如何成为数据库客户端一样。消息传递服务器与数据库服务器一样,具有客户端 API,应用程序可以使用该 API 与服务器进行交互。API 不是特定于应用程序的,而是特定于域的,其中域是消息传递的。应用程序必须包含一组将消息传递域与应用程序连接和统一的代码,以允许应用程序执行消息传递。
A server needs clients, and an application that uses messaging is a client of the messaging server. But applications do not necessarily know how to be messaging clients any more than they know how to be database clients. The messaging server, like a database server, has a client API that the application can use to interact with the server. The API is not application-specific but is domain-specific, where the domain is messaging. The application must contain a set of code that connects and unites the messaging domain with the application to allow the application to perform messaging.
|
使用消息端点将应用程序连接到消息传递通道,消息端点是消息传递系统的客户端,应用程序可以使用它来发送或接收消息。 Connect an application to a messaging channel using a Message Endpoint, a client of the messaging system that the application can then use to send or receive Messages. |
消息端点代码是应用程序和消息系统的客户端 API 自定义的。应用程序的其余部分对消息格式、消息传递通道或通过消息传递与其他应用程序进行通信的任何其他细节知之甚少。它只知道它有一个请求或数据要发送到另一个应用程序,或者期待来自另一个应用程序的请求或数据。消息传送端点代码接收该命令或数据,将其制作成消息,并将其发送到特定的消息传送通道。它是接收消息、提取内容并以有意义的方式将其提供给应用程序的端点。
Message Endpoint code is custom to both the application and the messaging system's client API. The rest of the application knows little about message formats, messaging channels, or any of the other details of communicating with other applications via messaging. It just knows that it has a request or piece of data to send to another application, or is expecting those from another application. It is the messaging endpoint code that takes that command or data, makes it into a message, and sends it on a particular messaging channel. It is the endpoint that receives a message, extracts the contents, and gives them to the application in a meaningful way.
消息端点将消息传递系统与应用程序的其余部分封装在一起,并为特定应用程序和任务定制通用消息传递 API。如果使用特定消息 API 的应用程序要切换到另一个,开发人员将必须重写消息端点代码,但应用程序的其余部分应保持不变。如果新版本的消息传递系统更改了消息传递 API,则这只会影响消息端点代码。如果应用程序决定通过消息传递以外的某种方式与其他人进行通信,那么开发人员理想情况下应该能够重写消息端点代码,但保持应用程序的其余部分不变。
The Message Endpoint encapsulates the messaging system from the rest of the application and customizes a general messaging API for a specific application and task. If an application using a particular messaging API were to switch to another, developers would have to rewrite the message endpoint code, but the rest of the application should remain the same. If a new version of a messaging system changes the messaging API, this should only affect the message endpoint code. If the application decides to communicate with others via some means other than messaging, developers should ideally be able to rewrite the message endpoint code but leave the rest of the application unchanged.
消息端点可用于发送或接收消息,但一个实例不能同时执行这两种操作。端点是特定于通道的,因此单个应用程序将使用多个端点与多个通道进行交互。应用程序可以使用多个端点实例来连接到单个通道,通常是为了支持多个并发线程。
A Message Endpoint can be used to send messages or receive them, but one instance does not do both. An endpoint is channel-specific, so a single application would use multiple endpoints to interface with multiple channels. An application may use multiple endpoint instances to interface to a single channel, usually to support multiple concurrent threads.
消息端点是一种专门的通道适配器,是为其应用程序定制开发并集成到其应用程序中的。
A Message Endpoint is a specialized Channel Adapter one that has been custom developed for and integrated into its application.
消息端点应设计为消息传递网关,以封装消息传递代码并向应用程序的其余部分隐藏消息系统。它可以使用消息传递映射器在域对象和消息之间传输数据。它可以被构造为服务激活器,以提供对同步服务或函数调用的异步消息访问。端点可以作为事务客户端显式地控制与消息传递系统的事务。
A Message Endpoint should be designed as a Messaging Gateway to encapsulate the messaging code and hide the message system from the rest of the application. It can employ a Messaging Mapper to transfer data between domain objects and messages. It can be structured as a Service Activator to provide asynchronous message access to a synchronous service or function call. An endpoint can explicitly control transactions with the messaging system as a Transactional Client.
发送消息非常容易,因此许多端点模式涉及接收消息的不同方法。消息接收者可以是轮询消费者或事件驱动消费者。多个消费者可以作为竞争消费者或通过消息调度程序从同一通道接收消息。接收者可以使用Selective Consumer 来决定使用或忽略哪些消息。它可以使用持久订阅者来确保订阅者不会错过端点断开连接时发布的消息。消费者可以是幂等接收者正确检测并处理重复消息。
Sending messages is pretty easy, so many endpoint patterns concern different approaches for receiving messages. A message receiver can be a Polling Consumer or an Event-Driven Consumer. Multiple consumers can receive messages from the same channel either as Competing Consumers or via a Message Dispatcher. A receiver can decide which messages to consume or ignore using a Selective Consumer. It can use a Durable Subscriber to make sure a subscriber does not miss messages published while the endpoint is disconnected. And the consumer can be an Idempotent Receiver that correctly detects and handles duplicate messages.
|
示例: JMS 生产者和消费者 Example: JMS Producer and Consumer 在 JMS 中,两个主要端点类型是用于发送和用于接收 。消息端点使用这些类型之一的实例向特定通道发送消息或从特定通道接收消息。 In JMS, the two main endpoint types are MessageProducer, for sending messages, and MessageConsumer, for receiving messages. A Message Endpoint uses an instance of one of these types to either send messages to or receive messages from a particular channel. |
|
示例: .NET 消息队列 Example: .NET MessageQueue 在 .NET 中,主端点类与主消息通道类MessageQueue相同。 消息端点使用MessageQueue的实例向特定通道发送消息或从特定通道接收消息。 In .NET, the main endpoint class is the same as the main Message Channel class, MessageQueue. A Message Endpoint uses an instance of MessageQueue to send messages to or receive messages from a particular channel. |
在第 3 章“消息系统”中,我们讨论了消息通道。当两个应用程序想要交换数据时,它们通过连接两个应用程序的通道发送数据来实现。发送数据的应用程序可能不知道哪个应用程序将接收数据。然而,通过选择发送数据的特定通道,发送方知道接收方将通过在该通道上查找来寻找此类数据。通过这种方式,生成共享数据的应用程序可以与那些希望使用该数据的应用程序进行通信。
In Chapter 3, "Messaging Systems," we discussed Message Channels. When two applications want to exchange data, they do so by sending the data through a channel that connects the two. The application sending the data may not know which application will receive the data. However, by selecting a particular channel on which to send the data, the sender knows that the receiver will be one that is looking for that sort of data by looking for it on that channel. In this way, the applications that produce shared data have a way to communicate with those that wish to consume it.
决定使用消息通道是很简单的部分;如果应用程序有数据要传输或希望接收数据,则它必须使用通道。挑战在于了解您的应用程序需要哪些渠道以及它们的用途。
Deciding to use a Message Channel is the simple part; if an application has data to transmit or data it wishes to receive, it will have to use a channel. The challenge is knowing what channels your applications will need and what to use them for.
固定通道集 本章讨论的 一个主题是消息通道集应用程序可用的内容往往是静态的。在设计应用程序时,开发人员必须知道在哪里放置什么类型的数据以便与其他应用程序共享该数据,以及在哪里查找来自其他应用程序的特定类型的数据。这些通信路径无法在运行时动态创建和发现;它们需要在设计时达成一致,以便应用程序知道其数据来自何处以及数据将去往何处。(虽然大多数通道确实必须静态定义,但也有例外,在这种情况下动态通道是实用且有用的。一个例外是请求-应答中的应答通道。请求者可以创建或获取回复者一无所知的新通道,并将其指定为请求消息的返回地址。然后回复者就可以使用它。另一个例外是支持分层通道的消息传递系统实现。接收者可以订阅层次结构中的父通道,然后发送者可以发布到接收者一无所知的新子通道。订阅者仍会收到该消息。尽管存在这些相对不寻常的情况,但通道通常是在部署之前定义的,并且应用程序是围绕一组已知的通道设计的。)
确定通道集 一个 相关的问题是,谁决定什么消息通道消息系统或应用程序可用吗?换句话说,消息传递系统是否定义了某些通道并要求应用程序使用这些通道?或者应用程序是否确定它们需要什么通道并要求消息系统提供它们?没有简单的答案;设计所需的通道集是迭代的。首先,应用程序确定消息系统需要提供哪些通道。后续应用程序将尝试围绕可用通道设计通信,但当这不切实际时,它们将要求添加额外的通道。当一组应用程序已经使用一组特定的通道并且新的应用程序想要加入时,它们也将使用现有的一组通道。
单向通道 另一个常见的混淆来源是消息通道是否是单向或双向的。从技术上讲,两者都不是;通道更像是一个存储桶,一些应用程序向其中添加数据,其他应用程序从中获取数据(尽管存储桶以某种协调的方式分布在多台计算机上)。但由于数据位于从一个应用程序传输到另一个应用程序的消息中,因此这就给出了通道方向,使其成为单向的。如果通道是双向的,则意味着应用程序将向同一通道发送消息并从同一通道接收消息,这虽然在技术上可能没有什么意义,因为应用程序往往会继续消耗自己的消息:它应该发送到的消息其他应用程序。因此,出于所有实际目的,通道是单向的。因此,要使两个应用程序进行双向对话,它们需要两个通道,每个方向一个(请参阅请求-答复将在下一章中介绍)。
Fixed set of channels One topic discussed in this chapter is that the set of Message Channels available to an application tends to be static. When designing an application, a developer must know where to put what types of data in order to share that data with other applications and likewise where to look for particular types of data coming from other applications. These paths of communication cannot be dynamically created and discovered at runtime; they need to be agreed upon at design time so that the application knows where its data is coming from and where the data is going to. (While it is true that most channels must be statically defined, there are exceptions, cases where dynamic channels are practical and useful. One exception is the reply channel in Request-Reply. The requestor can create or obtain a new channel that the replier knows nothing about and specify it as the Return Address of a request message. The replier can then use it. Another exception is messaging system implementations that support hierarchical channels. A receiver can subscribe to a parent in the hierarchy, and then a sender can publish to a new child channel that the receiver knows nothing about. The subscriber will still receive the message. These relatively unusual cases notwithstanding, channels are usually defined before deployment, and applications are designed around a known set of channels.)
Determining the set of channels A related issue is, Who decides what Message Channels are availablethe messaging system or the applications? In other words, does the messaging system define certain channels and require the applications to make do with those? Or do the applications determine what channels they need and require the messaging system to provide them? There is no simple answer; designing the needed set of channels is iterative. First, the applications determine which channels the messaging system needs to provide. Subsequent applications will try to design their communication around the channels that are available, but when this is not practical, they will require that additional channels be added. When a set of applications already uses a certain set of channels, and new applications want to join in, they too will use the existing set of channels. When existing applications add new functionality, they may require new channels.
Unidirectional channels Another common source of confusion is whether a Message Channel is unidirectional or bidirectional. Technically, it's neither; a channel is more like a bucket that some applications add data to and other applications take data from (albeit a bucket that is distributed across multiple computers in some coordinated fashion). But because the data is in messages that travel from one application to another, that gives the channel direction, making it unidirectional. If a channel were bidirectional, that would mean that an application would both send messages to and receive messages from the same channel, whichwhile technically possiblemakes little sense because the application would tend to keep consuming its own messages: the messages it's supposed to be sending to other applications. So, for all practical purposes, channels are unidirectional. As a consequence, for two applications to have a two-way conversation, they need two channels, one in each direction (see Request-Reply in the next chapter).
现在我们了解了消息通道是什么,让我们考虑使用它们所涉及的决策。
Now that we understand what Message Channels are, let's consider the decisions involved in using them.
一对一或一对多 当您的应用程序共享一段数据时,您希望仅与一个其他应用程序还是与任何其他感兴趣的应用程序共享它?要将数据发送到单个应用程序,请使用点对点通道。这并不能保证在该通道上发送的每条数据都一定会发送到同一个接收器,因为该通道可能有多个接收器。然而,它确实确保任何一份数据仅被一个应用程序接收。如果您希望所有接收器应用程序都能够接收数据,请使用Publish-Subscribe Channel 。当您以这种方式发送一段数据时,通道会有效地为每个接收者复制数据。
什么类型的数据 任何计算机内存中的任何数据都必须符合某种类型:具有商定含义的已知格式或预期结构。否则,所有数据都只是一堆字节,并且无法理解它。消息系统的工作方式大致相同;消息内容必须符合某种类型,以便接收者理解数据的结构。数据类型通道的理念是通道上的所有数据必须属于同一类型。这是消息传递系统需要大量通道的主要原因。如果数据可以是任何类型,则消息传递系统在任意两个应用程序之间只需要一个通道(在每个方向)。
无效消息和 死消息消息系统可以确保消息正确传递,但不能保证接收者知道如何处理它。接收者对数据的类型和含义有期望。当它收到不符合这些期望的消息时,它无能为力。不过,它可以做的就是将奇怪的消息放在专门指定的无效消息通道上,希望监视该通道的某些实用程序能够拾取该消息并找出如何处理它。许多消息传递系统都有类似的内置功能,即死信通道,对于已成功发送但最终无法成功投递的消息。同样,系统管理实用程序应该监视死信通道并决定如何处理无法传递的消息。
防崩溃 如果消息系统崩溃或关闭进行维护,其消息会发生什么情况?当它恢复并运行时,它的消息仍然在它的通道中吗?默认情况下,否;通道将其消息存储在内存中。然而,保证交付使通道持久化,以便它们的消息存储在磁盘上。这会损害性能,但会使消息传递更加可靠,即使消息传递系统并不可靠。
非消息传递客户端 如果应用程序无法连接到消息传递系统但仍想参与消息传递 怎么办?通常情况下,这会很不幸,但如果消息传递系统可以通过其用户界面、业务服务 API、数据库或网络连接(例如 TCP/IP 或 HTTP)以某种方式连接到应用程序,那么通道适配器就可以使用消息系统。这允许您将一个通道(或一组通道)连接到应用程序,而无需修改应用程序,并且可能不需要消息客户端与应用程序在同一台计算机上运行。有时,“非消息传递客户端”确实是消息传递客户端,但只是针对不同的消息传递系统。在这种情况下,作为两个消息传递系统上的客户端的应用程序可以在两者之间构建消息传递桥,从而有效地将它们连接到一个复合消息传递系统中。
通信主干 随着 越来越多的企业应用程序连接到消息传递系统并通过消息传递提供其功能,消息传递系统成为企业中共享功能的一站式采购的集中点。新应用程序只需要知道使用哪些通道来请求功能以及监听哪些其他通道来获取结果。消息传递系统本身本质上成为消息总线,它是提供对企业所有各种且不断变化的应用程序和功能的访问的主干网。通过从一开始就专门设计,您可以更快、更轻松地实现这种集成必杀技。
One-to-one or one-to-many When your application shares a piece of data, do you want to share it with just one other application or with any other application that is interested? To send the data to a single application, use a Point-to-Point Channel. This does not guarantee that every piece of data sent on that channel will necessarily go to the same receiver, because the channel might have multiple receivers. It does, however, ensure that any one piece of data will be received by only one of the applications. If you want all of the receiver applications to be able to receive the data, use a Publish-Subscribe Channel. When you send a piece of data this way, the channel effectively copies the data for each of the receivers.
What type of data Any data in any computer memory has to conform to some sort of type: a known format or expected structure with an agreed-upon meaning. Otherwise, all data would just be a bunch of bytes, and there would be no way to make any sense out of it. Messaging systems work much the same way; the message contents must conform to some type so that the receiver understands the data's structure. Datatype Channel is the idea that all of the data on a channel must be of the same type. This is the main reason that messaging systems need lots of channels. If the data could be of any type, the messaging system would only need one channel (in each direction) between any two applications.
Invalid and dead messages The message system can ensure that a message is delivered properly, but it cannot guarantee that the receiver will know what to do with it. The receiver has expectations about the data's type and meaning. When it receives a message that doesn't meet these expectations, there's not much it can do. What it can do, though, is put the strange message on a specially designated Invalid Message Channel in hopes that some utility monitoring the channel will pick up the message and figure out what to do with it. Many messaging systems have a similar built-in feature, a Dead Letter Channel, for messages that are successfully sent but ultimately cannot be successfully delivered. Again, a system management utility should monitor the Dead Letter Channel and decide what to do with the messages that could not be delivered.
Crash proof If the messaging system crashes or is shut down for maintenance, what happens to its messages? When it is back up and running, will its messages still be in its channels? By default, no; channels store their messages in memory. However, Guaranteed Delivery makes channels persistent so that their messages are stored on disk. This hurts performance but makes messaging more reliable, even when the messaging system isn't.
Non-messaging clients What if an application cannot connect to a messaging system but still wants to participate in messaging? Normally it would be out of luck, but if the messaging system can connect to the application somehowthrough its user interface, its business services API, its database, or a network connection such as TCP/IP or HTTPthen a Channel Adapter on the messaging system can be used. This allows you to connect a channel (or set of channels) to the application without having to modify the application and perhaps without requiring a messaging client running on the same machine as the application. Sometimes the "non-messaging client" really is a messaging client, but just for a different messaging system. In that case, an application that is a client on both messaging systems can build a Messaging Bridge between the two, effectively connecting them into one composite messaging system.
Communications backbone As more and more of an enterprise's applications connect to the messaging system and make their functionality available through messaging, the messaging system becomes a centralized point of one-stop shopping for shared functionality in the enterprise. A new application simply needs to know which channels to use to request functionality and which others to listen on for the results. The messaging system itself essentially becomes a Message Bus, a backbone providing access to all of the enterprise's various and ever-changing applications and functionality. You can achieve this integration nirvana more quickly and easily by specifically designing for it from the beginning.
正如您所看到的,为消息传递设置应用程序不仅仅涉及将它们连接到消息传递系统以便它们可以发送消息。消息必须有消息通道来传输。仅仅在某些渠道上进行打压也无法完成工作。它们的设计必须有一个目的,基于共享的数据类型、提供数据的应用程序类型以及接收数据的应用程序类型。本章解释了设计这些渠道时的决策。
As you can see, getting applications set up for Messaging involves more than just connecting them to the messaging system so that they can send messages. The messages must have Message Channels to transmit on. Simply slapping in some channels doesn't get the job done either. They have to be designed with a purpose, based on the datatype being shared, the sort of application making the data available, and the sort of application receiving the data. This chapter explains the decisions that go into designing these channels.
为了帮助说明这些模式,每个模式都提供了一个来自虚构的、简化的股票交易领域的示例。虽然这些示例都不应该用作实现真实交易系统的基础,但它们确实可以作为如何使用模式的简短而具体的示例。
To help illustrate the patterns, each one has an example from a fictitious, simplified stock trading domain. While none of these examples should be used as the basis for implementing a real trading system, they do serve as brief and specific examples of how the patterns can be used.
应用程序正在使用消息传递进行远程过程调用 (RPC) 或传输文档。
An application is using Messaging to make remote procedure calls (RPCs) or transfer documents.
|
呼叫者如何确定只有一位接收者会收到文档或执行呼叫? How can the caller be sure that exactly one receiver will receive the document or perform the call? |
RPC 的优点之一是它是在单个远程进程上调用的,因此接收方要么执行该过程,要么不执行(并且会发生异常)。由于接收方仅被调用一次,因此它仅执行该过程一次。但对于消息传递,一旦呼叫被打包为消息并放置在消息通道上,许多接收者可能会在通道上看到它并决定执行该过程。
One advantage of an RPC is that it's invoked on a single remote process, so either that receiver performs the procedure or it does not (and an exception occurs). And since the receiver was called only once, it performs the procedure only once. But with messaging, once a call is packaged as a Message and placed on a Message Channel, potentially many receivers could see it on the channel and decide to perform the procedure.
消息传递系统可以防止多个接收器监视单个通道,但这会不必要地限制希望向多个接收器传输数据的呼叫者。通道上的所有接收器可以进行协调,以确保只有其中一个接收器实际执行该过程,但这会很复杂,会产生大量通信开销,并且通常会增加其他独立接收器之间的耦合。单个通道上的多个接收器可能是理想的,以便可以同时使用多个消息,但任何一个接收器都应该使用任何单个消息。
The messaging system could prevent more than one receiver from monitoring a single channel, but this would unnecessarily limit callers that wish to transmit data to multiple receivers. All of the receivers on a channel could coordinate to ensure that only one of them actually performs the procedure, but that would be complex, create a lot of communications overhead, and generally increase the coupling between otherwise independent receivers. Multiple receivers on a single channel may be desirable so that multiple messages can be consumed concurrently, but any one receiver should consume any single message.
|
在点对点通道上发送消息,这可确保只有一个接收者会收到特定消息。 Send the message on a Point-to-Point Channel, which ensures that only one receiver will receive a particular message. |
点对点通道确保只有一个接收者消费任何给定的消息。通道可以有多个接收者,这些接收者可以同时消费多条消息,但只有其中一个可以成功消费某一特定消息。如果多个接收者尝试消费一条消息,通道会确保只有其中一个成功,因此接收者不必相互协调。
A Point-to-Point Channel ensures that only one receiver consumes any given message. The channel can have multiple receivers that can consume multiple messages concurrently, but only one of them can successfully consume a particular message. If multiple receivers try to consume a single message, the channel ensures that only one of them succeeds, so the receivers do not have to coordinate with each other.
当点对点通道只有一个消费者时,一条消息仅被消费一次这一事实并不奇怪。当通道有多个消费者时,它们就成为竞争消费者,并且通道确保只有一个消费者接收每条消息。这种设计使得消息的消费和处理具有高度的可扩展性,因为该工作可以在多台计算机上的多个应用程序中运行的多个消费者之间实现负载平衡。
When a Point-to-Point Channel has only one consumer, the fact that a message gets consumed only once is not surprising. When the channel has multiple consumers, then they become Competing Consumers, and the channel ensures that only one of the consumers receives each message. This design makes consuming and processing messages highly scalable because that work can be load-balanced across multiple consumers running in multiple applications on multiple computers.
您使用点对点通道仅向其中一个可用接收者发送消息,而要将消息发送至所有可用接收者,请使用发布-订阅通道。要使用消息传递实现 RPC,请使用带有一对点对点 Channel 的Request-Reply。调用是命令消息,回复是文档消息。
Whereas you use a Point-to-Point Channel to send a message to only one of the available receivers, to send a message to all available receivers, use a Publish-Subscribe Channel. To implement RPCs using messaging, use Request-Reply with a pair of Point-to-Point Channels. The call is a Command Message, and the reply is a Document Message.
|
示例: 股票交易 Example: Stock Trading 在股票交易系统中,进行特定交易的请求是一条消息,应该由一个接收者使用和执行,因此该消息应该放置在点对点通道上。 In a stock trading system, the request to make a particular trade is a message that should be consumed and performed by exactly one receiver, so the message should be placed on a Point-to-Point Channel. |
|
示例: JMS 队列 Example: JMS Queue 在 JMS 中,点对点通道实现了Queue接口。发送方使用QueueSender发送消息;每个接收者使用自己的QueueReceiver 来接收消息 [ JMS 1.1]、 [ Hapner ] 。 In JMS, a point-to-point channel implements the Queue interface. The sender uses a QueueSender to send messages; each receiver uses its own QueueReceiver to receive messages [JMS 1.1], [Hapner]. 应用程序使用QueueSender来发送如下消息: An application uses a QueueSender to send a message like this: Queuequeue = //通过JNDI获取队列 QueueConnectionFactoryfactory = // 获取连接工厂 Queue queue = // obtain the queue via JNDI QueueConnectionFactory factory = // obtain the connection factory via JNDI QueueConnection connection = factory.createQueueConnection(); QueueSession session = connection.createQueueSession(true, Session.AUTO_ACKNOWLEDGE); QueueSender sender = session.createSender(queue); Message message = session.createTextMessage("The contents of the message."); sender.send(message); 应用程序使用QueueReceiver 来接收如下消息: An application uses a QueueReceiver to receive a message like this: Queuequeue = //通过JNDI获取队列 QueueConnectionFactoryfactory = // 获取连接工厂 Queue queue = // obtain the queue via JNDI QueueConnectionFactory factory = // obtain the connection factory via JNDI QueueConnection connection = factory.createQueueConnection(); QueueSession session = connection.createQueueSession(true, Session.AUTO_ACKNOWLEDGE); QueueReceiver receiver = session.createReceiver(queue); TextMessage message = (TextMessage) receiver.receive(); String contents = message.getText(); 注意:JMS 1.1 统一了点对点和发布-订阅域的客户端 API,因此此处显示的代码可以简化为使用Destination 、 ConnectionFactory 、 Connection 、 Session 、 MessageProducer和MessageConsumer 而不是其特定于队列的对应项。 NOTE: JMS 1.1 unifies the client APIs for the point-to-point and publish-subscribe domains, so the code shown here can be simplified to use Destination, ConnectionFactory, Connection, Session, MessageProducer, and MessageConsumer rather than their Queue-specific counterparts. |
|
示例: .NET 消息队列 Example: .NET MessageQueue 在.NET中,MessageQueue类实现了点对点通道[ SysMsg]。实现.NET消息传递的MSMQ在3.0版本之前仅支持点对点消息传递,因此.NET支持点对点消息传递。JMS 将连接工厂、连接、会话、发送者和队列的职责分开,而 MessageQueue 则完成这一切。 In .NET, the MessageQueue class implements a point-to-point channel [SysMsg]. MSMQ, which implements .NET messaging, supported only point-to-point messaging prior to version 3.0, so point-to-point is what .NET supports. Whereas JMS separates the responsibilities of the connection factory, connection, session, sender, and queue, a MessageQueue does it all. 在MessageQueue上发送消息,如下所示: Send a message on a MessageQueue like this: MessageQueue 队列 = new MessageQueue("MyQueue");
queue.Send("消息的内容。");
MessageQueue queue = new MessageQueue("MyQueue");
queue.Send("The contents of the message.");
在MessageQueue上接收消息,如下所示: Receive a message on a MessageQueue like this: MessageQueue 队列 = new MessageQueue("MyQueue");
消息消息=queue.Receive();
字符串内容 = (String) message.Body();
MessageQueue queue = new MessageQueue("MyQueue");
Message message = queue.Receive();
String contents = (String) message.Body();
|
应用程序正在使用消息传递来宣布事件。
An application is using Messaging to announce events.
|
发送者如何向所有感兴趣的接收者广播事件? How can the sender broadcast an event to all interested receivers? |
幸运的是,有一些完善的实施广播的模式。观察者模式[ GoF]描述了将观察者与其主体(即事件的发起者)解耦的需要,以便主体可以轻松地向所有感兴趣的观察者提供事件通知,无论有多少观察者(甚至没有)。发布者-订阅者模式[ POSA ]通过添加用于通信事件通知的事件通道的概念来扩展观察者。
Luckily, there are well-established patterns for implementing broadcasting. The Observer pattern [GoF] describes the need to decouple observers from their subject (that is, the originator of the event) so that the subject can easily provide event notification to all interested observers no matter how many observers there are (even none). The Publisher-Subscriber pattern [POSA] expands upon Observer by adding the notion of an event channel for communicating event notifications.
这就是理论,但它如何与消息传递一起工作呢?事件可以打包为消息,以便消息传递能够可靠地将事件传达给观察者(订阅者)。那么,事件通道就是Message Channel 。但是消息传递通道如何将事件正确地传达给所有订阅者呢?
That's the theory, but how does it work with messaging? The event can be packaged as a Message so that messaging will reliably communicate the event to the observers (subscribers). Then, the event channel is a Message Channel. But how will a messaging channel properly communicate the event to all of the subscribers?
每个订阅者需要被通知一次特定事件,但不应该被重复通知同一事件。在通知所有订阅者之前,该事件不能被视为已消耗,但一旦通知了,该事件就可以被视为已消耗,并且应该从通道中消失。然而,让订阅者协调以确定何时使用消息违反了观察者模式的解耦。并发消费者不应该竞争,但应该能够共享事件消息。
Each subscriber needs to be notified of a particular event once but should not be notified repeatedly of the same event. The event cannot be considered consumed until all of the subscribers have been notified, but once they have, the event can be considered consumed and should disappear from the channel. Yet, having the subscribers coordinate to determine when a message is consumed violates the decoupling of the Observer pattern. Concurrent consumers should not compete but should be able to share the event message.
|
在发布-订阅通道上发送事件,该通道将特定事件的副本传递给每个接收者。 Send the event on a Publish-Subscribe Channel, which delivers a copy of a particular event to each receiver. |
发布-订阅通道的工作原理如下:它有一个输入通道,可分为多个输出通道,每个通道对应一个订阅者。当事件发布到通道中时,发布-订阅通道会将消息的副本传递到每个输出通道。通道的每个输出端只有一个订阅者,一条消息只能消费一次。通过这种方式,每个订阅者只收到一次消息,并且消耗的副本从他们的频道中消失。
A Publish-Subscribe Channel works like this: It has one input channel that splits into multiple output channels, one for each subscriber. When an event is published into the channel, the Publish-Subscribe Channel delivers a copy of the message to each of the output channels. Each output end of the channel has only one subscriber, which is allowed to consume a message only once. In this way, each subscriber gets the message only once, and consumed copies disappear from their channels.
发布-订阅通道可以是一个有用的调试工具。即使消息注定只能发送给单个接收者,但使用发布-订阅通道可以让您在不干扰现有消息流的情况下窃听消息通道。在调试消息传递应用程序时,监视通道上的所有流量非常有帮助。它还可以使您免于将大量打印语句插入到参与消息传递解决方案的每个应用程序中。创建一个程序来侦听所有活动通道上的消息并将其记录到文件中,可以提供与消息存储相同的许多好处。
A Publish-Subscribe Channel can be a useful debugging tool. Even though a message is destined to only a single receiver, using a Publish-Subscribe Channel allows you to eavesdrop on a message channel without disturbing the existing message flow. Monitoring all traffic on a channel can be tremendously helpful when debugging messaging applications. It can also save you from inserting a ton of print statements into each application that participates in the messaging solution. Creating a program that listens for messages on all active channels and logs them to a file can provide many of the same benefits that a Message Store brings.
然而,窃听发布-订阅通道的能力也可能成为一个缺点。如果您的消息传递解决方案在工资系统和会计系统之间传输工资数据,您可能不希望允许任何人编写简单的程序来侦听消息流量。点对点通道在一定程度上缓解了这个问题:因为窃听者会消耗通道外的消息,并且消息会突然丢失,所以可以很快检测到这种情况。然而,许多消息队列实现提供了查看功能,使消费者可以查看队列内的消息而不消耗任何消息。因此,订阅消息频道是应该受到安全策略限制的操作。许多(但不是全部)商业消息传递实现都实施了此类限制。此外,创建一个监控工具来记录消息通道的活动订阅者可能是一个有用的系统管理工具。
However, the ability to eavesdrop on a Publish-Subscribe Channel can also turn into a disadvantage. If your messaging solution transmits payroll data between the payroll system and the accounting system, you may not want to allow anyone to write a simple program to listen to the message traffic. Point-to-Point Channels alleviate the problem somewhat: Because the eavesdropper would consume messages off the channel and messages would suddenly be missing, the situation could be detected very quickly. However, many message queue implementations provide peek functions that let consumers look at messages inside a queue without consuming any of the messages. As a result, subscribing to a Message Channel is an operation that should be restricted by security policies. Many (but not all) commercial messaging implementations implement such restrictions. In addition, creating a monitoring tool that logs active subscribers to Message Channels can be a useful systems management tool.
通配符订阅者Wildcard Subscribers许多消息系统允许发布-订阅通道的订阅者指定特殊的通配符。这是一项强大的技术,允许订阅者同时订阅多个频道。例如,如果应用程序将消息发布到通道MyCorp/Prod/OrderProcessing/NewOrders和MyCorp/Prod/OrderProcessing/CancelledOrders ,则应用程序可以订阅 MyCorp /Prod/OrderProcessing/* 并接收与订单处理相关的所有消息。另一个应用程序可以订阅MyCorp/Dev/**接收开发环境中所有应用程序发送的所有消息。仅允许订阅者使用通配符;发布者始终需要将消息发布到特定渠道。通配符订阅者的具体功能和语法因不同消息传递供应商而异。 Many messaging systems allow subscribers to Publish-Subscribe Channels to specify special wildcard characters. This is a powerful technique to allow subscribers to subscribe to multiple channels at once. For example, if an application publishes messages to the channels MyCorp/Prod/OrderProcessing/NewOrders and MyCorp/Prod/OrderProcessing/CancelledOrders, an application could subscribe to MyCorp/Prod/OrderProcessing/* and receive all messages related to order processing. Another application could subscribe to MyCorp/Dev/** to receive all messages sent by all applications in the development environment. Only subscribers are allowed to use wildcards; publishers are always required to publish a message to a specific channel. The specific capabilities and syntax for wildcard subscribers vary between the different messaging vendors. |
事件消息通常在发布,因为多个依赖者通常对一个事件感兴趣。订阅者可以是持久订阅者,也可以是非持久订阅者,请参阅第 10章“消息传送端点”中的持久订阅者。如果订阅者应确认通知,请使用Request-Reply,其中通知是请求,确认是回复。将每条消息存储在发布-订阅通道上直到所有订阅者都使用该消息可能需要大量的消息存储。为了帮助缓解此问题,发送到发布-订阅通道的消息可以使用消息过期。
An Event Message is usually sent on a Publish-Subscribe Channel because multiple dependents are often interested in an event. A subscriber can be durable or nondurablesee Durable Subscriber in the Chapter 10, "Messaging Endpoints." If notifications should be acknowledged by the subscribers, use Request-Reply, where the notification is the request and the acknowledgment is the reply. Storing each message on a Publish-Subscribe Channel until all subscribers consume the message can require a large amount of message storage. To help alleviate this issue, messages sent to a Publish-Subscribe Channel can use Message Expiration.
|
示例: 股票交易 Example: Stock Trading 在股票交易系统中,许多系统可能需要收到交易完成的通知,因此将它们全部设为发布交易完成的发布-订阅通道的订阅者。 In a stock trading system, many systems may need to be notified of the completion of a trade, so make them all subscribers of a Publish-Subscribe Channel that publishes trade completions. 同样,许多消费者对显示或处理当前股票报价数据感兴趣。因此,股票报价应该通过发布-订阅通道进行广播。 Likewise, many consumers are interested in displaying or processing current stock quote data. Therefore, stock quotes should be broadcast across a Publish-Subscribe Channel. |
|
示例: JMS 主题 Example: JMS Topic 在 JMS 中,发布-订阅通道实现主题接口。发送者使用TopicPublisher发送消息;每个接收者使用自己的TopicSubscriber 来接收消息 [JMS 1.1],[ Hapner ] 。 In JMS, a Publish-Subscribe Channel implements the Topic interface. The sender uses a TopicPublisher to send messages; each receiver uses its own TopicSubscriber to receive messages [JMS 1.1], [Hapner]. 应用程序使用 TopicPublisher发送如下消息: An application uses a TopicPublisher to send a message like this: topic topic = // 通过JNDI获取主题 TopicConnectionFactoryfactory = // 获取连接工厂 Topic topic = // obtain the topic via JNDI TopicConnectionFactory factory = // obtain the connection factory via JNDI TopicConnection connection = factory.createTopicConnection(); TopicSession session = connection.createTopicSession(true, Session.AUTO_ACKNOWLEDGE); TopicPublisher publisher = session.createPublisher(topic); Message message = session.createTextMessage("The contents of the message."); publisher.publish(message); 应用程序使用TopicSubscriber 来接收如下消息: An application uses a TopicSubscriber to receive a message like this: topic topic = // 通过JNDI获取主题 TopicConnectionFactoryfactory = // 获取连接工厂 Topic topic = // obtain the topic via JNDI TopicConnectionFactory factory = // obtain the connection factory via JNDI TopicConnection connection = factory.createTopicConnection(); TopicSession session = connection.createTopicSession(true, Session.AUTO_ACKNOWLEDGE); TopicSubscriber subscriber = session.createSubscriber(topic); TextMessage message = (TextMessage) subscriber.receive(); String contents = message.getText(); 注意:JMS 1.1 统一了点对点和发布-订阅域的客户端 API,因此此处显示的代码可以简化为使用Destination 、 ConnectionFactory 、 Connection 、 Session 、 MessageProducer和MessageConsumer 而不是其特定于主题的对应项。 NOTE: JMS 1.1 unifies the client APIs for the point-to-point and publish-subscribe domains, so the code shown here can be simplified to use Destination, ConnectionFactory, Connection, Session, MessageProducer, and MessageConsumer rather than their Topic-specific counterparts. |
|
示例: MSMQ 一对多消息传递 Example: MSMQ One-to-Many Messaging MSMQ 3.0 [ MSMQ ]中的一个新功能是一对多消息传递模型,它有两种不同的方法。 A new feature in MSMQ 3.0 [MSMQ] is a one-to-many messaging model, which has two different approaches.
.NET 公共语言运行时 (CLR) 不提供对使用一对多消息传递模型的直接支持。但是,可以通过 COM 接口 [ MDMSG ]访问此功能,该接口可以嵌入到 .NET 代码中。 The .NET Common Language Runtime (CLR) does not provide direct support for using the one-to-many messaging model. However, this functionality can be accessed through the COM interface [MDMSG], which can be embedded in .NET code. |
|
示例: 简单消息传递 Example: Simple Messaging 第 6 章“插曲:简单消息传递”中的 JMS 发布-订阅示例显示了如何使用消息传递跨多个进程实现 Observer的示例。 The JMS Publish-Subscribe example in Chapter 6, "Interlude: Simple Messaging," shows an example of how to implement Observer across multiple processes using messaging. |
应用程序使用消息传递来传输不同类型的数据,例如不同类型的文档。
An application is using Messaging to transfer different types of data, such as different types of documents.
|
应用程序如何发送数据项以便接收者知道如何处理它? How can the application send a data item such that the receiver will know how to process it? |
所有消息都只是消息系统定义的相同消息类型的实例,并且任何消息的内容最终都只是一个字节数组。虽然这种简单的结构(字节束)对于消息传递系统能够传输消息而言足够具体,但对于接收者能够处理消息内容而言,它还不够具体。
All messages are just instances of the same message type, as defined by the messaging system, and the contents of any message are ultimately just a byte array. While this simple structurea bundle of bytesis specific enough for a messaging system to be able to transmit a message, it is not specific enough for a receiver to be able to process a message's contents.
接收者必须知道消息内容的数据结构和数据格式。结构可以是字符数组、字节数组、序列化对象、XML文档等。格式可以是字节或字符的记录结构、序列化对象的类、XML 文档的模式定义等。所有这些知识都被宽松地称为消息类型,指的是消息内容的结构和格式。
A receiver must know the message content's data structure and data format. The structure could be character array, byte array, serialized object, XML document, and so on. The format could be the record structure of the bytes or characters, the class of the serialized object, the schema definition of the XML document, and so on. All of this knowledge is loosely referred to as the message's type, referring to both the structure and format of the message's contents.
接收者必须知道它正在接收什么类型的消息,否则它不知道如何处理它们。例如,发送者可能希望发送不同的对象,例如采购订单、报价和查询。然而,接收器可能会采取不同的步骤来处理每一个,因此它必须知道哪个是哪个。如果发送方只是通过单个消息通道将所有这些发送到接收方,则接收方将不知道如何处理每一项。
The receiver must know what type of messages it's receiving, or it won't know how to process them. For example, a sender may wish to send different objects such as purchase orders, price quotes, and queries. Yet, a receiver will probably take different steps to process each of those, so it has to know which is which. If the sender simply sends all of these to the receiver via a single message channel, the receiver will not know how to process each one.
混合数据类型
Mixed Data Types
发送者知道它正在发送什么消息类型,那么如何将其传达给接收者呢?发送者可以在消息的标头中放置一个标志(请参阅格式指示符),但接收者将需要一个 case 语句。发送方可以将数据包装在命令消息中,对于每种类型的数据使用不同的命令,但是当消息试图做的只是将数据传输到接收方时,这会告诉接收方如何处理数据。
The sender knows what message type it's sending, so how can this be communicated to the receiver? The sender could put a flag in the message's header (see Format Indicator), but then the receiver will need a case statement. The sender could wrap the data in a Command Message with a different command for each type of data, but that presumes to tell the receiver what to do with the data when all that the message is trying to do is transmit the data to the receiver.
处理项目集合时,会出现与消息传递不同的类似问题。该集合必须是同类的,这意味着所有项目都是相同的类型,以便处理器知道项目的类型是什么以及如何操作它。许多集合实现并不强制所有项目都属于特定类型,因此程序员必须设计代码以确保集合中的所有项目都属于同一类型。否则,可以将不同类型的项目添加到集合中,但处理这些项目的代码将不知道如何操作每个项目,因为它不知道特定项目实现的类型。
A similar problemone separate from messagingoccurs when processing a collection of items. The collection must be homogeneous, meaning that all of the items are the same type, so that the processor knows what an item's type is and thus how to manipulate it. Many collection implementations do not force all of the items to be of a specific type, so programmers must design their code to ensure that all of the items in a collection are of the same type. Otherwise, items of different types can be added to a collection, but the code processing those items will not know how to manipulate each one because it won't know what type a particular item implements.
同样的原则也适用于消息传递,因为通道上的消息同样必须属于同一类型。最简单的解决方案是所有消息都采用相同的格式。如果格式必须不同,则它们都必须有可靠的格式指示符。虽然通道并不强制所有消息都属于同一类型,但接收者需要它们是同一类型,以便知道如何处理它们。
The same principle applies to messaging because the messages on a channel must likewise all be of the same type. The simplest solution is for all of the messages to be of the same format. If the formats must differ, they must all have a reliable format indicator. While the channel does not force all messages to be of the same type, the receiver needs them to be so that it knows how to process them.
|
为每个数据类型使用单独的数据类型通道,以便特定通道上的所有数据都具有相同的类型。 Use a separate Datatype Channel for each datatype so that all data on a particular channel is of the same type. |
通过对每种数据类型使用单独的数据类型通道,给定通道上的所有消息将包含相同类型的数据。发送者知道数据的类型,需要选择适当的通道来发送数据。接收者知道数据是通过哪个通道接收的,也就知道它是什么类型。
By using a separate Datatype Channel for each type of data, all of the messages on a given channel will contain the same type of data. The sender, knowing what type the data is, will need to select the appropriate channel to send it on. The receiver, knowing what channel the data was received on, will know what type it is.
如图所示,由于发送方要发送三种不同类型的数据(采购订单、报价和查询),因此应该使用三种不同的通道。发送项目时,发送者必须为该项目选择适当的数据类型通道。当接收一个项目时,接收者根据接收该项目的数据类型通道知道该项目的类型。
As shown in the figures, since the sender wants to send three different types of data (purchase orders, price quotes, and queries), it should use three different channels. When sending an item, the sender must select the appropriate Datatype Channel for that item. When receiving an item, the receiver knows the item's type because of the datatype channel on which it received the item.
服务质量渠道Quality-of-Service Channel一个相关的策略是服务质量渠道。有时,企业希望传输具有与另一组消息不同的服务级别的一组消息。例如,传入的新订单消息可能是企业收入的主要来源,并且应该通过非常可靠的渠道进行传输(例如,使用保证交付))尽管存在潜在的性能开销。另一方面,丢失代表订单状态请求的消息并不是世界末日,因此我们可能倾向于使用更快但不太可靠的通道。有时这可以在单个通道上完成,例如通过使用消息优先级。然而,通常最好在创建通道时定义服务质量参数,而不是将决定权留给发送消息的应用程序代码。因此,最好在其自己的信道上传输每组消息,以便可以根据其消息组的需要来调整每个信道的服务质量。 A related strategy is a Quality-of-Service Channel. Sometimes, an enterprise wants to transmit one group of messages with a different level of service from another group of messages. For example, incoming New Order messages may be the primary source of revenue for a business and should be transported over a very reliable channel (e.g., using Guaranteed Delivery) despite the potential performance overhead. On the other hand, losing a message that represents a request for order status is not the end of the world, so we may be inclined to use a faster but less reliable channel. This can sometimes be accomplished on a single channel, for example, by using message priorities. However, generally it is a better idea to define quality-of-service parameters when creating the channel rather than leaving the decision up to the application code that sends the messages. Therefore, it is best to transmit each group of messages on its own channel so that each channel's quality-of-service can be tuned for the needs of its group of messages. |
正如消息通道中所讨论的,通道很便宜,但它们不是免费的。应用程序可能需要传输许多不同的数据类型,太多以至于无法为每个数据类型创建单独的数据类型通道。在这种情况下,多个数据类型可以通过为每种类型使用不同的选择性消费者来共享单个通道。这使得单个物理通道就像多个逻辑Datatype Channel一样(一种称为多路复用的) 。数据类型通道解释了为什么通道上的所有消息必须具有相同的格式,规范数据模型解释了企业中所有渠道上的所有消息应如何遵循统一的数据模型。
As discussed under Message Channel, channels are cheap, but they are not free. An application may need to transmit many different datatypes, too many to create a separate Datatype Channel for each. In this case, multiple datatypes can share a single channel by using a different Selective Consumer for each type. This makes a single physical channel act like multiple logical Datatype Channels (a strategy called multiplexing). Whereas Datatype Channel explains why all messages on a channel must be of the same format, Canonical Data Model explains how all messages on all channels in an enterprise should follow a unified data model.
如果我们想使用Datatype Channel但现有的消息发布者只是将所有消息发送到单个通道,我们可以使用基于内容路由器来解复用消息。路由器将消息流划分为多个Datatype Channel ,每个 Datatype Channel 仅承载一种类型的消息。
If we would like to use Datatype Channels but an existing message publisher simply sends all messages to a single channel, we can use a Content-Based Router to demultiplex the messages. The router divides the message stream across multiple Datatype Channels, each of which carries messages of only one type.
消息调度程序除了提供并发消息消费之外,还可以用于以特定于类型的方式处理一组通用消息。每个消息必须指定其类型(通常通过消息头中的格式指示符);调度程序检测消息的类型并将其调度到特定于类型的执行者进行处理。通道上的消息仍然都是同一类型,但该类型是调度程序支持的更通用的类型,而不是各个执行者需要的更具体的类型。
A Message Dispatcher, besides providing concurrent message consumption, can be used to process a generic set of messages in type-specific ways. Each message must specify its type (typically by a format indicator in the message's header); the dispatcher detects the message's type and dispatches it to a type-specific performer for processing. The messages on the channel are still all of the same type, but that type is the more general one that the dispatcher supports, not the more specific ones that the various performers require.
格式指示符用于区分同一数据的不同格式版本,从而使这些不同的格式能够在同一数据类型通道上发送。
A Format Indicator is used to distinguish different format versions of the same data, which in turn enables these different formats to be sent on the same Datatype Channel.
|
示例: 股票交易 Example: Stock Trading 在股票交易系统中,如果报价请求的格式与交易请求的格式不同,则系统应使用单独的数据类型通道来通信每种请求。同样,地址变更公告可能与投资组合经理变更公告具有不同的格式,因此每种公告都应该有自己的Datatype Channel 。 In a stock trading system, if the format of a quote request is different from that of a trade request, the system should use a separate Datatype Channel for communicating each kind of request. Likewise, a change-of-address announcement may have a different format from a change-of-portfolio-manager announcement, so each kind of announcement should have its own Datatype Channel. |
An application is using Messaging to receive Messages.
|
消息接收者如何优雅地处理接收到的毫无意义的消息? How can a messaging receiver gracefully handle receiving a message that makes no sense? |
理论上,消息通道上的所有内容都只是消息,消息接收者只是处理消息。然而,要处理消息,接收者必须能够解释其数据并理解其含义。这并不总是可能的:消息正文可能会导致解析错误、词汇错误或验证错误。消息标头可能缺少所需的属性,或者属性值可能没有意义。发送者可能会将完美的消息放到错误的通道上,从而将其传输给错误的接收者。恶意发送者可能故意发送不正确的消息,只是为了扰乱接收者。接收者可能无法处理它收到的所有消息,因此它必须有其他方式来处理它认为无效的消息。
In theory, everything on a Message Channel is just a message, and message receivers just process messages. However, to process a message, a receiver must be able to interpret its data and understand its meaning. This is not always possible: The message body may cause parsing errors, lexical errors, or validation errors. The message header may be missing needed properties, or the property values may not make sense. A sender might put a perfectly good message on the wrong channel, transmitting it to the wrong receiver. A malicious sender could purposely send an incorrect message just to mess up the receiver. A receiver may not be able to process all the messages it receives, so it must have some other way to handle messages it does not consider valid.
消息通道应该是数据类型通道,其中通道上的每条消息都应该具有该通道的正确数据类型。如果发送方在通道上放置的消息的数据类型不正确,则消息传递系统将成功传输该消息,但接收方将无法识别该消息并且不知道如何处理该消息。
A Message Channel should be a Datatype Channel, where each of the messages on the channel is supposed to be of the proper datatype for that channel. If a sender puts a message on the channel that is not the correct datatype, the messaging system will transmit the message successfully, but the receiver will not recognize the message and will not know how to process it.
数据类型或格式不正确的消息的一个示例是通道上本应包含文本消息的字节消息。另一个示例是格式不正确的消息,例如格式不正确或对于商定的 DTD 或模式无效的 XML 文档。就消息系统而言,这些消息没有任何问题,但接收者将无法处理它们,因此它们是无效的。
One example of a message with an improper datatype or format is a byte message on a channel that is supposed to contain text messages. Another example is a message whose format is not correct, such as an XML document that is not well formed or that is not valid for the agreed-upon DTD or schema. There's nothing wrong with these messages as far as the messaging system is concerned, but the receiver will not be able to process them, so they are invalid.
不包含接收者期望的标头字段值的消息也是无效的。如果消息应该具有标头属性,例如相关标识符、消息序列标识符、返回地址等,但消息缺少这些属性,则消息传递系统将正确传递消息,但接收者不会能够成功处理它。
Messages that do not contain the header field values that the receiver expects are also invalid. If a message is supposed to have header properties such as a Correlation Identifier, Message Sequence identifiers, a Return Address, and so on, but the message is missing the properties, then the messaging system will deliver the message properly, but the receiver will not be able to process it successfully.
无效消息
Invalid Message
当接收方发现它尝试处理的消息无效时,它应该如何处理该消息?它可以将消息放回到通道上,但随后该消息将被同一接收者或其他类似的接收者重新使用。同时,被忽略的无效消息会使通道变得混乱并损害性能。接收者可以使用无效消息并将其丢弃,但这往往会隐藏需要检测的消息传递问题。系统需要的是一种方法,将不正确的消息从通道中清除,并将它们放在不妨碍但可以检测到的位置,以诊断消息传递系统的问题。
When the receiver discovers that the message it's trying to process is not valid, what should it do with the message? It could put the message back on the channel, but then the message will just be reconsumed by the same receiver or another like it. Meanwhile, invalid messages that are being ignored will clutter the channel and hurt performance. The receiver could consume the invalid message and throw it away, but that would tend to hide messaging problems that need to be detected. What the system needs is a way to clean improper messages out of channels and put them in a place where they will be out of the way but can be detected to diagnose problems with the messaging system.
|
接收者应将不正确的消息移至无效消息通道,这是一个用于接收者无法处理的消息的特殊通道。 The receiver should move the improper message to an Invalid Message Channel, a special channel for messages that could not be processed by their receivers. |
在设计供应用程序使用的消息系统时,管理员必须定义一个或多个Invalid Message Channel供应用程序使用。无效消息通道不会用于正常、成功的通信,因此如果它混杂着不正确的消息,那不会是问题。想要诊断不正确消息的错误处理程序可以使用无效通道上的接收器来检测可用的消息。
When designing a messaging system for applications to use, the administrator must define one or more Invalid Message Channels for the applications to use. The Invalid Message Channel will not be used for normal, successful communication, so if it is cluttered with improper messages, that will not be a problem. An error handler that wants to diagnose improper messages can use a receiver on the invalid channel to detect messages as they become available.
无效消息通道就像消息传递的错误日志。当应用程序出现问题时,最好记录错误。当处理消息时出现问题时,最好将消息放在无效消息的通道上。如果浏览频道的任何人都不清楚为什么此消息无效,则应用程序还应该记录包含更多详细信息的错误。
An Invalid Message Channel is like an error log for messaging. When something goes wrong in an application, it's a good idea to log the error. When something goes wrong processing a message, it's a good idea to put the message on the channel for invalid messages. If it won't be obvious to anyone browsing the channel why this message is invalid, the application should also log an error with more details.
请记住,消息本质上既不是有效的也不是无效的,而是由接收者的上下文和期望做出此决定。一条消息对一个接收者可能有效,但对另一个接收者可能无效;两个这样的接收器不应共享同一频道。对通道上的一个接收者有效的消息也应该对该通道上的所有其他接收者有效。同样,如果一个接收者认为一条消息无效,那么所有其他接收者也应该如此。发送者有责任确保其在通道上发送的消息将被通道的接收者视为有效。否则,接收者将通过将发送者的消息重新路由到无效消息通道来忽略发送者的消息。
Keep in mind that a message is neither inherently valid nor invalid, but it is the receiver's context and expectations that make this determination. A message that may be valid for one receiver may be invalid for another receiver; two such receivers should not share the same channel. A message that is valid for one receiver on a channel should be valid for all other receivers on that channel. Likewise, if one receiver considers a message invalid, all other receivers should as well. It is the sender's responsibility to make sure that a message it sends on a channel will be considered valid by the channel's receivers. Otherwise, the receivers will ignore the sender's messages by rerouting them to the Invalid Message Channel.
当消息结构正确但其内容在语义上不正确时,会出现类似但独立的问题。例如,命令消息可以指示接收者删除不存在的数据库记录。这不是消息传递错误,而是应用程序错误。因此,虽然将消息移至无效消息通道可能很诱人,但该消息没有任何问题,因此将其视为无效消息会产生误导。相反,像这样的错误应该作为无效的应用程序请求来处理,而不是无效的消息。
A similar but separate problem occurs when a message is structured properly, but its contents are semantically incorrect. For example, a Command Message may instruct the receiver to delete a database record that does not exist. This is not a messaging error but an application error. As such, while it may be tempting to move the message to the Invalid Message Channel, there is nothing wrong with the message, so treating it as invalid is misleading. Rather, an error like this should be handled as an invalid application request, not an invalid message.
当接收器被实现为服务激活器或消息传递网关时,消息处理错误和应用程序错误之间的区别变得更加简单和清晰。这些模式将消息处理代码与应用程序的其余部分分开。如果处理消息时发生错误,则该消息无效,应移至无效消息通道。如果在应用程序处理消息中的数据时发生这种情况,则这是一个与消息传递无关的应用程序错误。
This difference between message-processing errors and application errors becomes simpler and clearer when the receiver is implemented as a Service Activator or Messaging Gateway. These patterns separate message-processing code from the rest of the application. If an error occurs while processing the message, the message is invalid and should be moved to the Invalid Message Channel. If it occurs while the application processes the data from the message, that is an application error that has nothing to do with messaging.
内容被忽略的无效消息通道与被忽略的错误日志一样有用。无效消息通道上的消息表明应用程序集成存在问题,因此不应忽略这些消息;相反,应该对它们进行分析以确定出了什么问题,以便解决问题。理想情况下,这将是一个自动化过程,用于消耗无效消息、确定其原因并修复根本问题。然而,原因通常是编码或配置错误,需要开发人员或系统分析师进行评估和修复。至少,使用消息传递和Invalid Message Channel 的应用程序应该有一个进程来监视无效的消息通道,并在通道包含消息时向系统管理员发出警报。
An Invalid Message Channel whose contents are ignored is about as useful as an error log that is ignored. Messages on the Invalid Message Channel indicate application integration problems, so those messages should not be ignored; rather, they should be analyzed to determine what went wrong so that the problem can be fixed. Ideally, this would be an automated process that consumed invalid messages, determined their cause, and fixed the underlying problems. However, the cause is often a coding or configuration error that requires a developer or system analyst to evaluate and repair. At the very least, applications that use messaging and Invalid Message Channels should have a process that monitors the Invalid Message Channel and alerts system administrators whenever the channel contains messages.
许多消息传递系统实现的类似概念是死信通道。无效消息通道适用于可以传递和接收但未处理的消息,而死信通道适用于消息传递系统无法正确传递的消息。
A similar concept implemented by many messaging systems is a Dead Letter Channel. Whereas an Invalid Message Channel is for messages that can be delivered and received but not processed, a Dead Letter Channel is for messages that the messaging system cannot deliver properly.
|
示例: 股票交易 Example: Stock Trading 在股票交易系统中,用于执行交易请求的应用程序可能会收到当前报价的请求,或者未指定购买什么证券或多少股票的交易请求,或者未指定向谁购买的交易请求。发送交易确认。在任何这些情况下,应用程序都会收到无效消息,该消息不满足应用程序处理交易请求所需的最低要求。一旦应用程序确定消息无效,它应该将消息重新发送到无效消息通道。发送交易请求的各种应用程序可能希望监视无效消息通道以确定他们的请求是否被丢弃。 In a stock trading system, an application for executing trade requests might receive a request for a current price quote, or a trade request that does not specify what security to buy or how many shares, or a trade request that does not specify to whom to send the trade confirmation. In any of these cases, the application has received an invalid messageone that does not meet the minimum requirements necessary for the application to be able to process the trade request. Once the application determines the message to be invalid, it should resend the message onto the Invalid Message Channel. The various applications that send trade requests may wish to monitor the Invalid Message Channel to determine if their requests are being discarded. |
|
示例: JMS 规范 Example: JMS Specification 在 JMS 中,规范建议,如果 MessageListener收到无法处理的消息,则行为良好的侦听器应将消息转移到“某种形式的特定于应用程序的‘无法处理的消息’目的地”[JMS 1.1] 。 这个无法处理的消息目的地是无效消息通道。 In JMS, the specification suggests that if a MessageListener gets a message it cannot process, a well-behaved listener should divert the message "to some form of application-specific 'unprocessable message' destination" [JMS 1.1]. This unprocessable message destination is an Invalid Message Channel. |
|
示例: 简单消息传递 Example: Simple Messaging JMS 请求-答复示例和 .NET 请求-答复示例(均在第 6 章“插曲:简单消息传递”中)显示了如何实现接收器的示例,这些接收器将它们无法处理的消息重新路由到无效消息通道。 The JMS Request-Reply example and .NET Request-Reply example (both in Chapter 6, "Interlude: Simple Messaging") show an example of how to implement receivers that reroute messages they cannot process to an Invalid Message Channel. |
一家企业正在使用消息传递来集成应用程序。
An enterprise is using Messaging to integrate applications.
|
消息系统将如何处理它无法传递的消息? What will the messaging system do with a message it cannot deliver? |
如果接收方收到无法处理的消息,则应将无效消息移至无效消息通道。但是,如果消息传递系统无法首先将消息传递给接收者怎么办?
If a receiver receives a message it cannot process, it should move the invalid message to an Invalid Message Channel. But what if the messaging system cannot deliver the message to the receiver in the first place?
消息传递系统可能无法传递消息的原因有多种。消息传递系统可能没有正确配置消息的通道。消息的通道可以在消息发送之后但在传送之前或在等待接收时被删除。消息在传送之前可能会过期(请参阅消息过期) 。如果消息在很长一段时间内无法传送,则没有明确过期的消息可能会超时。具有所有选择性消费者都忽略的选择值的消息将永远不会被阅读,并且最终可能会消亡。消息的标头可能存在问题,导致其无法成功传递。
There are a number of reasons the messaging system may not be able to deliver a message. The messaging system may not have the message's channel configured properly. The message's channel may be deleted after the message is sent but before it can be delivered or while it is waiting to be received. The message may expire before it can be delivered (see Message Expiration). A message without an explicit expiration may nevertheless time out if it cannot be delivered for a very long time. A message with a selection value that all Selective Consumers ignore will never be read and may eventually die. A message could have something wrong with its header that prevents it from being delivered successfully.
一旦消息传递系统确定它无法传递消息,它就必须对该消息执行某些操作。它可能会将消息留在任何地方,从而使系统变得混乱。它可以尝试将消息返回给发送者,但发送者不是接收者,无法检测到传递。它可能只是删除消息并希望没有人错过它,但这很可能会给已成功发送消息并期望消息被传递(并接收和处理)的发送者带来问题。
Once the messaging system determines that it cannot deliver a message, it has to do something with the message. It could just leave the message wherever it is, cluttering up the system. It could try to return the message to the sender, but the sender is not a receiver and cannot detect deliveries. It could just delete the message and hope no one misses it, but this may well cause a problem for the sender that has successfully sent the message and expects it to be delivered (and received and processed).
|
当消息传递系统确定它不能或不应该传递消息时,它可以选择将消息移动到死信通道。 When a messaging system determines that it cannot or should not deliver a message, it may elect to move the message to a Dead Letter Channel. |
死信通道的具体工作方式取决于特定消息传递系统的实现(如果它提供了一种实现)。该通道可以称为“死消息队列”[ Monson-Haefel ] 或“死信队列”[ MQSeries ] 、 [ Dickman ] 。通常,安装消息系统的每台机器都有自己的本地死信通道,因此无论消息在什么机器上死亡,它都可以从一个本地队列移动到另一个本地队列,而不会出现任何网络不确定性。这也记录了消息在哪台机器上死亡。当消息传递系统移动消息时,它还可以记录消息应该传递的原始通道。
The specific way a Dead Letter Channel works depends on the specific messaging system's implementation, if it provides one at all. The channel may be called a "dead message queue" [Monson-Haefel] or "dead letter queue" [MQSeries], [Dickman]. Typically, each machine the messaging system is installed on has its own local Dead Letter Channel so that whatever machine a message dies on, it can be moved from one local queue to another without any networking uncertainties. This also records what machine the message died on. When the messaging system moves the message, it may also record the original channel on which the message was supposed to be delivered.
死消息和无效消息之间的区别在于,消息传递系统无法成功传递它认为是死消息的消息,而无效消息可以正确传递,但无法被接收者处理。确定是否应将消息移至死信通道是由消息传递系统执行的对消息标头的评估。另一方面,接收方将消息移动到无效消息通道由于接收者感兴趣的消息正文或特定标头字段。对于接收者来说,死消息的确定和处理似乎是自动的,而接收者必须自行处理无效消息。使用消息传递系统的开发人员会被消息传递系统提供的任何无效消息处理所困扰,但她可以设计自己的无效消息处理,包括处理消息传递系统无法处理的看似无效的消息。
The difference between a dead message and an invalid one is that the messaging system cannot successfully deliver what it then deems a dead message, whereas an invalid message is properly delivered but cannot be processed by the receiver. Determining if a message should be moved to the Dead Letter Channel is an evaluation of the message's header performed by the messaging system. On the other hand, the receiver moves a message to an Invalid Message Channel because of the message's body or particular header fields the receiver is interested in. To the receiver, determination and handling of dead messages seem automatic, whereas the receiver must handle invalid messages itself. A developer using a messaging system is stuck with whatever dead message handling the messaging system provides, but she can design her own invalid message handling, including handling for seemingly dead messages that the messaging system doesn't handle.
|
示例: 股票交易 Example: Stock Trading 在股票交易系统中,希望执行交易的应用程序可以发送交易请求。为了确保在合理的时间内(也许少于五分钟)收到交易,请求者将请求的消息过期时间设置为五分钟。如果消息传递系统无法在该时间内传递请求,或者交易应用程序没有及时接收消息(例如,从通道中读取消息),则消息传递系统将从交易请求中删除该消息通道并将消息放入死信通道。交易系统可能希望监视系统的死信通道以确定是否丢失了交易。 In a stock trading system, an application that wishes to perform a trade can send a trade request. To make sure that the trade is received in a reasonable amount of time (less than five minutes, perhaps), the requestor sets the request's Message Expiration to five minutes. If the messaging system cannot deliver the request in that amount of time, or if the trading application does not receive the message (e.g., read it off of the channel) in time, then the messaging system will take the message off of the trade request channel and put the message on the Dead Letter Channel. The trading system may wish to monitor the system's Dead Letter Channels to determine if it is missing trades. |
一家企业正在使用消息传递来集成应用程序。
An enterprise is using Messaging to integrate applications.
|
即使消息系统出现故障,发送者如何确保消息能够送达? How can the sender make sure that a message will be delivered even if the messaging system fails? |
与 RPC 相比,异步消息传递的主要优点之一是发送方、接收方以及连接两者的网络不必同时工作。如果网络不可用,消息传递系统会存储消息,直到网络可用。同样,如果接收者不可用,消息传递系统会存储消息并重试传递,直到接收者可用。这是消息传递所基于的存储和转发过程。那么,消息在转发之前应该存储在哪里呢?
One of the main advantages of asynchronous messaging over RPC is that the sender, the receiver, and network connecting the two don't all have to be working at the same time. If the network is not available, the messaging system stores the message until the network becomes available. Likewise, if the receiver is unavailable, the messaging system stores the message and retries delivery until the receiver becomes available. This is the store-and-forward process that messaging is based on. So, where should the message be stored before it is forwarded?
默认情况下,消息传递系统将消息存储在内存中,直到它能够成功地将消息转发到下一个存储点。只要消息传递系统可靠运行,这种方法就有效,但如果消息传递系统崩溃(例如,由于其中一台计算机断电或消息传递进程意外中止),则存储在内存中的所有消息都会丢失。
By default, the messaging system stores the message in memory until it can successfully forward the message to the next storage point. This works as long as the messaging system is running reliably, but if the messaging system crashes (for example, because one of its computers loses power or the messaging process aborts unexpectedly), all of the messages stored in memory are lost.
大多数应用程序都必须处理类似的问题。如果应用程序崩溃,存储在内存中的所有数据都会丢失。为了防止这种情况,应用程序使用文件和数据库将数据保存到磁盘,以便数据在系统崩溃时幸存下来。消息系统需要类似的方式来更持久地保存消息,这样即使系统崩溃也不会丢失消息。
Most applications have to deal with similar problems. All data that is stored in memory is lost if the application crashes. To prevent this, applications use files and databases to persist data to disk so that it survives system crashes. Messaging systems need a similar way to persist messages more permanently so that no message gets lost even if the system crashes.
|
使用保证传递使消息持久化,这样即使消息系统崩溃它们也不会丢失。 Use Guaranteed Delivery to make messages persistent so that they are not lost even if the messaging system crashes. |
通过保证交付,消息传递系统使用内置数据存储来保存消息。每台安装消息系统的计算机都有自己的数据存储,以便可以在本地存储消息。当发送者发送消息时,只有在消息安全地存储在发送者的数据存储中时,发送操作才能成功完成。随后,消息不会从一个数据存储中删除,直到成功转发到并存储在下一个数据存储中。这样,一旦发送方成功发送消息,该消息将始终存储在至少一台计算机的磁盘上,直到消息成功传递给接收方并被接收方确认为止。
With Guaranteed Delivery, the messaging system uses a built-in datastore to persist messages. Each computer on which the messaging system is installed has its own datastore so that the messages can be stored locally. When the sender sends a message, the send operation does not complete successfully until the message is safely stored in the sender's datastore. Subsequently, the message is not deleted from one datastore until it is successfully forwarded to and stored in the next datastore. In this way, once the sender successfully sends the message, it is always stored on disk on at least one computer until it is successfully delivered to and acknowledged by the receiver.
持久性可提高可靠性,但会牺牲性能。因此,如果在消息传递系统崩溃或关闭时丢失消息是可以接受的,请避免使用保证传递,这样消息将更快地在消息传递系统中移动。
Persistence increases reliability but at the expense of performance. Thus, if it's okay to lose messages when the messaging system crashes or is shut down, avoid using Guaranteed Delivery so messages will move through the messaging system faster.
还要考虑到保证交付在高流量场景中可能会消耗大量磁盘空间。如果生产者每秒生成数百或数千条消息,那么持续数小时的网络中断可能会占用大量磁盘空间。由于网络不可用,消息必须存储在生产计算机的本地磁盘驱动器上,而该驱动器可能无法容纳这么多数据。由于这些原因,某些消息传递系统允许您配置重试超时指定消息在消息传递系统内缓冲多长时间的参数。在一些高流量应用中(例如,将股票报价流式传输到终端),该超时可能必须设置为较短的时间跨度,例如几分钟。幸运的是,在许多此类应用程序中,消息被用作事件消息,并且可以在短时间内安全地丢弃(请参阅消息过期) 。
Also consider that Guaranteed Delivery can consume a large amount of disk space in high-traffic scenarios. If a producer generates hundreds or thousands of messages per second, then a network outage that lasts multiple hours could use up a huge amount of disk space. Because the network is unavailable, the messages have to be stored on the producing computer's local disk drive, which may not be designed to hold this much data. For these reasons, some messaging systems allow you to configure a retry timeout parameter that specifies how long messages are buffered inside the messaging system. In some high-traffic applications (e.g., streaming stock quotes to terminals), this timeout may have to be set to a short time span, for example, a few minutes. Luckily, in many of these applications, messages are used as Event Messages and can safely be discarded after a short amount of time elapses (see Message Expiration).
在测试和调试期间关闭保证交付也很有用。这样可以通过停止并重新启动消息传递服务器轻松清除所有消息通道。即使是简单的消息传递程序,仍在排队的消息也会使调试变得非常乏味。例如,您可能有一个通过点对点通道连接的发送者和接收者。如果消息仍然存储在通道上,接收方将在发送方生成任何新消息之前处理该消息。这是异步、有保证的消息传递中的常见调试陷阱。许多商业消息传递实现还允许您单独清除队列,以便在测试期间重新启动(请参阅Channel Purger)。
It can also be useful to turn off Guaranteed Delivery during testing and debugging. This makes it easy to purge all message channels by stopping and restarting the messaging server. Messages that are still queued up can make it very tedious to debug even simple messaging programs. For example, you may have a sender and a receiver connected by a Point-to-Point Channel. If a message is still stored on the channel, the receiver will process that message before any new message that the sender produces. This is a common debugging pitfall in asynchronous, guaranteed messaging. Many commercial messaging implementations also allow you to purge queues individually to allow a fresh restart during testing (see Channel Purger).
保证消息传递的保证如何?How Guaranteed Is Guaranteed Messaging?重要的是要记住,计算机系统的可靠性往往以“9 的数量”来衡量,换句话说,99.9%。这告诉我们,有些东西很少是 100% 可靠的,成本已经呈指数级增长,从 99.9% 上升到 99.99%。同样的注意事项也适用于保证交付。总会有消息丢失的情况。例如,如果存储持久消息的磁盘发生故障,消息可能会丢失。您可以通过使用冗余磁盘存储来降低发生故障的可能性,从而使磁盘存储更加可靠。这可能会在可靠性评级上再增加一个“9”,但可能不会达到真正的 100%。另外,如果网络长时间不可用,必须存储的消息可能会填满计算机的磁盘,从而导致消息丢失。总之,保证传送旨在保护消息传送免受预期中断(例如机器故障或网络故障)的影响,但它通常不是 100% 万无一失。 It is important to keep in mind that reliability in computer systems tends to be measured in the "number of 9s"in other words, 99.9 percent. This tells us that something is rarely 100 percent reliable, with the cost already increasing exponentially to move from 99.9 to 99.99 percent. The same caveats apply to Guaranteed Delivery. There will always be a scenario where a message can get lost. For example, if the disk that stores the persisted messages fails, messages may get lost. You can make your disk storage more reliable by using redundant disk storage to reduce the likelihood of failure. This will possibly add another "9" to the reliability rating but likely not make it a true 100 percent. Also, if the network is unavailable for a long time, the messages that have to be stored may fill up the computer's disk, resulting in lost messages. In summary, Guaranteed Delivery is designed to protect the message delivery from expected outages, such as machine failures or network failures, but it is usually not 100 percent bulletproof. |
对于 .NET 的 MSMQ 实现,要使通道持久化,必须将其声明为事务性的,这意味着发送方通常必须是事务性客户端。在 JMS 中,通过发布-订阅通道,保证交付仅确保消息将交付给活动订阅者。为了确保订阅者即使在不活动时也能收到消息,订阅者需要是持久订阅者。
With .NET's MSMQ implementation, for a channel to be persistent, it must be declared transactional, which means senders usually have to be Transactional Clients. In JMS, with Publish-Subscribe Channel, Guaranteed Delivery only ensures that the messages will be delivered to the active subscribers. To ensure that a subscriber receives messages even when it's inactive, the subscriber will need to be a Durable Subscriber.
|
示例: 股票交易 Example: Stock Trading 在股票交易系统中,交易请求和交易确认可能应该通过保证交付来发送,以帮助确保不会丢失。地址变更公告应与保证交付 一起发送,但可能没有必要与价格更新一起发送,因为丢失其中一些通知并不重要,而且它们的频率使保证交付的开销令人望而却步。 In a stock trading system, trade requests and trade confirmations should probably be sent with Guaranteed Delivery to help ensure that none are lost. Change-of-address announcements should be sent with Guaranteed Delivery, but it is probably not necessary with price updates because losing some of them is not significant, and their frequency makes the overhead of Guaranteed Delivery prohibitive. 在持久订阅者中,股票交易示例表明一些价格变化订阅者可能希望持久。如果是这样,那么也许价格变动渠道也应该保证交货。然而,其他订户可能不需要持久或希望承受保证交付的开销。如何满足这些不同的需求?系统可能希望实现两种价格更改渠道,一种具有保证交付,另一种则不具有。只有需要所有更新的订阅者才应该订阅持久通道,并且他们的订阅应该是持久的。由于开销增加,发布者可能希望在持久通道上不太频繁地发布更新。(参见服务质量通道策略在数据类型通道下讨论。 ) In Durable Subscriber, the stock trading example says that some price-change subscribers may wish to be durable. If so, then perhaps the price-change channel should guarantee delivery as well. Yet other subscribers may not need to be durable or want to suffer the overhead of Guaranteed Delivery. How can these different needs be met? The system may wish to implement two price-change channels, one with Guaranteed Delivery and another without. Only subscribers that require all updates should subscribe to the persistent channel, and their subscriptions should be durable. The publisher may wish to publish updates less frequently on the persistent channel because of its increased overhead. (See the Quality-of-Service Channel strategy discussed under Datatype Channel.) |
|
示例: JMS 持久消息 Example: JMS Persistent Messages 在 JMS 中,可以基于每个消息设置消息持久性。换句话说,特定通道上的某些消息可能是持久的,而其他消息可能不是[ JMS 1.1 ]、[ Hapner ]。 In JMS, message persistence can be set on a per-message basis. In other words, some messages on a particular channel may be persistent, whereas others might not be [JMS 1.1], [Hapner]. 当 JMS 发送方想要使消息持久化时,它使用其MessageProducer 将消息为 PERSISTENT 。 发送者可以按消息设置持久性,如下所示: When a JMS sender wants to make a message persistent, it uses its MessageProducer to set the message's JMSDeliveryMode to PERSISTENT. The sender can set persistence on a per-message basis like this: session session = //获取session
目的地目的地= //获取目的地
Message message = // 创建消息
MessageProducer 生产者 = session.createProducer(destination);
生产者.发送(
信息,
javax.jms.DeliveryMode.PERSISTENT,
javax.jms.Message.DEFAULT_PRIORITY,
javax.jms.Message.DEFAULT_TIME_TO_LIVE);
Session session = // obtain the session
Destination destination = // obtain the destination
Message message = // create the message
MessageProducer producer = session.createProducer(destination);
producer.send(
message,
javax.jms.DeliveryMode.PERSISTENT,
javax.jms.Message.DEFAULT_PRIORITY,
javax.jms.Message.DEFAULT_TIME_TO_LIVE);
如果应用程序希望使所有消息持久化,则可以将其设置为消息生产者的默认值。 If the application wants to make all of the messages persistent, it can set that as the default for the message producer. Producer.setDeliveryMode(javax.jms.DeliveryMode.PERSISTENT); producer.setDeliveryMode(javax.jms.DeliveryMode.PERSISTENT); (事实上,消息生产者的默认传递模式是持久化的。)现在,该生产者发送的消息会自动持久化,因此可以简单地发送它们。 (And, in fact, the default delivery mode for a message producer is persistent.) Now, messages sent by this producer are automatically persistent, so they can simply be sent. 生产者.发送(消息); producer.send(message); 同时,同一通道上的其他消息生产者发送的消息可能是持久的,具体取决于这些生产者如何配置其消息。 Meanwhile, messages sent by other message producers on the same channel may be persistent, depending on how those producers configure their messages. |
|
示例: IBM WebSphere MQ Example: IBM WebSphere MQ 在 WebSphere MQ 中,可以基于每个通道或每个消息设置保证交付。如果通道不是持久化的,那么消息就不能持久化。如果通道是持久的,则可以配置通道,使得在该通道上发送的所有消息自动持久,或者可以持久或非持久地发送单独的消息。 In WebSphere MQ, Guaranteed Delivery can be set on a per-channel basis or a per-message basis. If the channel is not persistent, the messages cannot be persistent. If the channel is persistent, the channel can be configured such that all messages sent on that channel are automatically persistent or that an individual message can be sent persistently or nonpersistently. 当在消息传递系统中创建通道时,通道被配置为持久(或非持久)。例如,可以配置通道以使其所有消息都是持久的。 A channel is configured to be persistent (or not) when it is created in the messaging system. For example, the channel can be configured so that all of its messages will be persistent. 定义 Q(myQueue) PER(PERS) DEFINE Q(myQueue) PER(PERS) 或者可以配置通道,以便消息发送者可以为每条消息指定该消息是持久的还是瞬态的。 Or the channel can be configured so that the message sender can specify with each message whether the message is persistent or transient. 定义 Q(myQueue) PER(APP) DEFINE Q(myQueue) PER(APP) 如果通道设置为允许发送者指定持久性,那么 JMS MessageProducer 可以设置该传递模式属性,如前所述。如果通道设置为使所有消息持久化,则 MessageProducer 指定的传递模式设置将被忽略[ WSMQ ] 。 If the channel is set to allow the sender to specify persistency, then a JMS MessageProducer can set that delivery-mode property as described earlier. If the channel is set to make all messages persistent, then the delivery-mode settings specified by the MessageProducer are ignored [WSMQ]. |
|
示例: .NET 持久消息 Example: .NET Persistent Messages 在 .NET 中,持久消息是通过将MessageQueue 设为事务性来创建的。 With .NET, persistent messages are created by making a MessageQueue transactional. MessageQueue.Create("MyQueue", true);
MessageQueue.Create("MyQueue", true);
在此队列上发送的所有消息将自动持久化 [ Dickman ]。 All messages sent on this queue will automatically be persistent [Dickman]. |
许多企业使用消息传递来集成多个不同的应用程序。
Many enterprises use Messaging to integrate multiple, disparate applications.
|
如何将应用程序连接到消息传递系统以便它可以发送和接收消息? How can you connect an application to the messaging system so that it can send and receive messages? |
大多数应用程序并非设计用于与消息传递基础设施配合使用。造成这种限制的原因有多种。许多应用程序都是作为独立的独立解决方案开发的,尽管它们包含可由其他系统利用的数据或功能。例如,许多大型机应用程序被设计为一体化应用程序,永远不需要与其他应用程序交互。唉,遗留集成现在是企业集成解决方案最常见的集成点之一。另一个原因是许多面向消息的中间件系统公开专有的 API,因此应用程序开发人员必须编写与消息传递系统的多个接口,
Most applications were not designed to work with a messaging infrastructure. There are a variety of reasons for this limitation. Many applications were developed as self-contained, standalone solutions even though they contain data or functionality that can be leveraged by other systems. For example, many mainframe applications were designed as a one-in-all application that would never have to interface with other applications. Alas, legacy integration is nowadays one of the most common integration points for enterprise integration solutions. Another reason results from the fact that many message-oriented middleware systems expose proprietary APIs so that an application developer would have to code multiple interfaces to the messaging system, one for each potential middleware vendor.
如果应用程序需要与其他应用程序交换数据,它们通常被设计为使用更通用的接口机制,例如文件交换或数据库表。读写文件是一项基本的操作系统功能,不依赖于特定于供应商的 API。同样,大多数业务应用程序已经将数据保存到数据库中,因此只需很少的额外工作即可将发往其他系统的数据存储在数据库表中。或者,应用程序可以在通用 API 中公开内部函数,供任何其他集成策略(包括消息传递)使用。
If applications need to exchange data with other applications, they often are designed to use more generic interface mechanisms such as file exchange or database tables. Reading and writing files is a basic operating system function and does not depend on vendor-specific APIs. Likewise, most business applications already persist data into a database, so little extra effort is required to store data destined for other systems in a database table. Or an application can expose internal functions in a generic API that can be used by any other integration strategy, including messaging.
其他应用程序可能能够通过 HTTP 或 TCP/IP 等简单协议进行通信。然而,这些协议不提供与消息通道相同的可靠性,并且应用程序使用的数据格式通常特定于应用程序并且与通用消息传递解决方案不兼容。
Other applications may be capable of communicating via a simple protocol like HTTP or TCP/IP. However, these protocols do not provide the same reliability as a Message Channel, and the data format used by the application is usually specific to the application and not compatible with a common messaging solution.
对于自定义应用程序,我们可以向应用程序添加代码以允许其发送和接收消息。然而,这可能会给应用程序带来额外的复杂性,我们需要小心,在进行这些更改时不要引入任何不需要的副作用。此外,这种方法要求开发人员精通应用程序逻辑和消息传递 API。这两种方法还假设我们可以访问应用程序源代码。如果我们处理从第三方软件供应商购买的打包应用程序,我们甚至可能无法选择更改应用程序代码。
In the case of custom applications, we could add code to the application to allow it to send and receive messages. However, this can introduce additional complexity into the application and we need to be careful not to introduce any undesired side effects when making these changes. Also, this approach requires developers to be skilled with both the application logic and the messaging API. Both those approaches also assume that we have access to the application source code. If we deal with a packaged application that we purchased from a third-party software vendor, we may not even have the option of changing the application code.
|
使用可以访问应用程序的 API 或数据的通道适配器,以基于此数据在通道上发布消息,并且同样可以接收消息并调用应用程序内部的功能。 Use a Channel Adapter that can access the application's API or data to publish messages on a channel based on this data and that likewise can receive messages and invoke functionality inside the application. |
适配器充当消息传递系统的消息传递客户端,并通过应用程序提供的接口调用应用程序功能。同样,通道适配器可以侦听应用程序内部事件并调用消息传递系统来响应这些事件。这样,任何应用程序只要有适当的Channel Adapter 就可以连接到消息系统并与其他应用程序集成。
The adapter acts as a messaging client to the messaging system and invokes application functions via an application-supplied interface. Likewise, the Channel Adapter can listen to application-internal events and invoke the messaging system in response to these events. This way, any application can connect to the messaging system and be integrated with other applications as long as it has a proper Channel Adapter.
通道适配器可以连接到应用程序体系结构的不同层,具体取决于该体系结构和消息传递系统需要访问的数据类型。
The Channel Adapter can connect to different layers of the application's architecture, depending on that architecture and the type of data the messaging system needs to access.
连接到应用程序不同层的通道适配器
A Channel Adapter Connecting to Different Layers of an Application
用户界面适配器。 这些类型的适配器有时被轻蔑地称为“屏幕抓取”,在许多情况下都非常有效。例如,应用程序可能在消息系统不支持的平台上实现。或者应用程序的所有者可能对支持集成兴趣不大。这消除了运行通道适配器的选项在应用平台上。然而,用户界面通常可从其他机器和平台(例如,3270 终端)获得。此外,基于 Web 的瘦客户端架构的兴起也引起了用户界面集成的一定程度的复兴。基于 HTML 的用户界面使得发出 HTTP 请求并解析结果变得非常容易。用户界面集成的另一个优点是不需要直接访问应用程序内部。在某些情况下,可能不希望或不可能将系统的内部功能暴露给集成解决方案。使用用户界面适配器,其他应用程序可以像普通用户一样访问该应用程序。用户界面适配器的缺点是解决方案潜在的脆弱性和低速度。应用程序必须解析“用户”输入并呈现屏幕作为响应,以便通道适配器可以将屏幕解析回原始数据。此过程涉及许多不必要的步骤,而且可能很慢。此外,用户界面的变化往往比核心应用程序逻辑更频繁。每次用户界面发生变化时,通道适配器可能也必须更改。
User Interface Adapter. Sometimes disparagingly called "screen scraping," these types of adapters can be very effective in many situations. For example, an application may be implemented on a platform that is not supported by the messaging system. Or the owner of the application may have little interest in supporting the integration. This eliminates the option of running the Channel Adapter on the application platform. However, the user interface is usually available from other machines and platforms (e.g., 3270 terminals). Also, the surge of Web-based thin-client architectures has caused a certain revival of user interface integration. HTML-based user interfaces make it very easy to make an HTTP request and parse out the results. Another advantage of user interface integration is that no direct access to the application internals is needed. In some cases, it may not be desirable or possible to expose internal functions of a system to the integration solution. Using a user interface adapter, other applications have the exact same access to the application as a regular user. The downside of user interface adapters is the potential brittleness and low speed of the solution. The application has to parse "user" input and render a screen in response just so that the Channel Adapter can parse the screen back into raw data. This process involves many unnecessary steps, and it can be slow. Also, user interfaces tend to change more frequently than the core application logic. Every time the user interface changes, the Channel Adapter is likely to have to be changed as well.
业务逻辑适配器。 大多数业务应用程序将其核心功能公开为 API。该接口可以是一组组件(例如,EJB、COM 对象、CORBA 组件)或直接编程API(例如,C++、C# 或Java 库)。由于软件供应商(或开发人员)明确公开这些 API 供其他应用程序访问,因此它们往往比用户界面更稳定。在大多数情况下,访问 API 也更加高效。一般来说,如果应用程序公开定义良好的 API,那么这种类型的通道适配器可能是最好的方法。
Business Logic Adapter. Most business applications expose their core functions as an API. This interface may be a set of components (e.g., EJBs, COM objects, CORBA components) or a direct programming API (e.g., a C++, C#, or Java library). Since the software vendor (or developer) exposes these APIs expressly for access by other applications, they tend to be more stable than the user interface. In most cases, accessing the API is also more efficient. In general, if the application exposes a well-defined API, this type of Channel Adapter is likely to be the best approach.
数据库适配器。 大多数业务应用程序将其数据保存在关系数据库中。由于信息已经在数据库中,因此通道适配器可以直接从数据库中提取信息,而应用程序不会注意到,这是一种非常非侵入性的应用程序集成方式。Channel Adapter甚至可以向相关表添加触发器,并在每次这些表中的数据发生变化时发送消息。这种类型的通道适配器由于只有两三个数据库供应商主导了关系数据库市场,这一事实可能非常高效并且非常普遍。这使我们能够使用相对通用的适配器连接到许多不同的应用程序。数据库适配器的缺点是我们要深入研究应用程序的内部。如果我们只是读取数据,这可能没有那么危险,但直接更新数据库可能非常危险。此外,许多应用程序供应商认为数据库模式“未发布”,这意味着他们保留随意更改它的权利,这可能会使数据库适配器解决方案变得脆弱。
Database Adapter. Most business applications persist their data inside a relational database. Since the information is already in the database, the Channel Adapter can extract information directly from the database without the application ever noticing, which is a very nonintrusive way to integrate the application. The Channel Adapter can even add a trigger to the relevant tables and send messages every time the data in these tables changes. This type of Channel Adapter can be very efficient and is quite universal, aided by the fact that only two or three database vendors dominate the market for relational databases. This allows us to connect to many different applications with a relatively generic adapter. The downside of a database adapter is that we are poking around deep in the internals of an application. This may not be as risky if we simply read data, but making updates directly to the database can be very dangerous. Also, many application vendors consider the database schema "unpublished," meaning that they reserve the right to change it at will, which can make a database adapter solution brittle.
Channel Adapter的一个重要限制是它们可以将消息转换为应用程序函数,但它们需要与所适配的组件的实现非常相似的消息格式。例如,数据库适配器通常要求传入消息的消息字段名称与应用程序数据库中的表和字段的名称相同。这种消息格式完全由应用程序的内部结构驱动,在与其他应用程序集成时不是一个好的消息格式。因此,大多数Channel Adapter必须与Message将应用程序特定的消息转换为符合规范数据模型的消息格式。
An important limitation of Channel Adapters is that they can convert messages into application functions, but they require message formatting that closely resembles the implementation of the components being adapted. For example, a database adapter typically requires the message field names of incoming messages to be the same as the names of tables and fields in the application database. This kind of message format is driven entirely by the internal structure of the application and is not a good message format to use when integrating with other applications. Therefore, most Channel Adapters must be combined with a Message Translator to convert the application-specific message into a message format that complies with the Canonical Data Model.
通道适配器通常可以在与应用程序或数据库本身不同的计算机上运行。通道适配器可以通过 HTTP 或 ODBC 等协议连接到应用程序逻辑或数据库。虽然这种设置允许我们避免在应用程序或数据库服务器上安装额外的软件,但这些协议通常不提供与消息传递通道相同的服务质量,例如保证交付。因此,我们必须意识到与数据库的远程连接可能代表潜在的故障点。
Channel Adapters can often run on a different computer than the application or the database itself. The Channel Adapter can connect to the application logic or the database via protocols such as HTTP or ODBC. While this setup allows us to avoid installing additional software on the application or database server, these protocols typically do not provide the same quality of service that a messaging channel provides, such as guaranteed delivery. Therefore, we must be aware that the remote connection to the database can represent a potential point of failure.
有些Channel Adapter是单向的。例如,如果Channel Adapter 通过 HTTP连接到应用程序,它可能只能消费消息并调用应用程序上的函数,但它可能无法检测应用程序数据的变化,除非通过重复轮询,这可以是效率很低。
Some Channel Adapters are unidirectional. For example, if a Channel Adapter connects to an application via HTTP, it may only be able to consume messages and invoke functions on the application, but it may not be able to detect changes in the application data except through repeated polling, which can be very inefficient.
通道适配器的一个有趣的变体是元数据适配器,有时称为设计时适配器。这种类型的适配器不调用应用程序功能,而是提取元数据,即描述应用程序内部数据格式的数据。然后,该元数据可用于配置消息转换器或检测应用程序数据格式的更改(请参阅第 8 章中的介绍),“消息转换”)。许多应用程序接口支持这种类型的元数据提取。例如,大多数商业数据库提供一组系统表,其中包含应用程序表描述形式的元数据。同样,大多数组件框架(例如,J2EE、.NET)都提供特殊的“反射”功能,允许组件枚举另一个组件提供的方法。
An interesting variation of the Channel Adapter is the Metadata Adapter, sometimes called Design-Time Adapter. This type of adapter does not invoke application functions but extracts metadata, data that describes the internal data formats of the application. This metadata can then be used to configure Message Translators or to detect changes in the application data formats (see the introduction in Chapter 8, "Message Transformation"). Many application interfaces support this type of metadata extraction. For example, most commercial databases provide a set of system tables that contain metadata in the form of descriptions of the application tables. Likewise, most component frameworks (e.g., J2EE, .NET) provide special "reflection" functions that allow a component to enumerate methods provided by another component.
通道适配器的一种特殊形式是消息传递桥。消息传递桥将消息传递系统连接到另一个消息传递系统,而不是特定的应用程序。通常,通道适配器被实现为事务客户端,以确保适配器所做的每一项工作在消息传递系统和正在适应的其他系统中都成功。
A special form of the Channel Adapter is the Messaging Bridge. The Messaging Bridge connects the messaging system to another messaging system as opposed to a specific application. Typically, a Channel Adapter is implemented as a Transactional Client to ensure that each piece of work the adapter does succeeds in both the messaging system and the other system being adapted.
|
示例: 股票交易 Example: Stock Trading 股票交易系统可能希望在数据库表中保存所有股票价格的日志。消息传送系统可以包括关系数据库适配器,其将来自通道的每条消息记录到指定的表和模式。此通道到 RDBMS 的适配器是Channel Adapter 。系统还能够从互联网(TCP/IP或HTTP)接收外部报价请求,并将它们与内部报价请求一起在其内部报价请求通道上发送。此 Internet 到通道适配器是Channel Adapter 。 A stock trading system may wish to keep a log of all of a stocks' prices in a database table. The messaging system may include a relational database adapter that logs each message from a channel to a specified table and schema. This channel-to-RDBMS adapter is a Channel Adapter. The system may also be able to receive external quote requests from the Internet (TCP/IP or HTTP) and send them on its internal quote-request channel with the internal quote requests. This Internet-to-channel adapter is a Channel Adapter. |
|
示例: 商业 EAI 工具 Example: Commercial EAI Tools 商业 EAI 供应商提供了一系列Channel Adapter作为其产品的一部分。拥有适用于所有可用主要应用程序包的适配器大大简化了集成解决方案的开发。大多数供应商还提供更通用的数据库适配器以及软件开发工具包 (SDK) 来开发自定义适配器。 Commercial EAI vendors provide a collection of Channel Adapters as part of their offerings. Having adapters to all major application packages available greatly simplifies development of an integration solution. Most vendors also provide more generic database adapters as well as software development kits (SDKs) to develop custom adapters. |
|
示例: 旧平台适配器 Example: Legacy Platform Adapters 许多供应商提供从通用消息传递系统到在 UNIX、MVS、OS/2、AS/400、Unisys 和 VMS 等平台上执行的遗留系统的适配器。这些适配器中的大多数都是特定于某个消息传递系统的。例如,Envoy Technologies 的 EnvoyMQ 是一个通道适配器,可将许多传统平台与 MSMQ 连接起来。它由在旧计算机上运行的客户端组件和在带有 MSMQ 的 Windows 计算机上运行的服务器组件组成。 A number of vendors provide adapters from common messaging system to legacy systems executing on platforms such as UNIX, MVS, OS/2, AS/400, Unisys, and VMS. Most of these adapters are specific to a certain messaging system. For example, Envoy Technologies' EnvoyMQ is a Channel Adapter that connects many legacy platforms with MSMQ. It consists of a client component that runs on the legacy computer and a server component that runs on a Windows computer with MSMQ. |
|
示例: Web 服务适配器 Example: Web Services Adapters 许多消息传递系统提供通道适配器来在 HTTP 传输和消息传递系统之间移动 SOAP 消息。这样,SOAP 消息就可以使用可靠的异步消息传递系统在 Intranet 上传输,并使用 HTTP 在全球 Internet(并穿过防火墙)上传输。IBM 的 WebSphere Application Server 的 Web 服务网关就是此类适配器的一个示例。 Many messaging systems provide Channel Adapters to move SOAP messages between an HTTP transport and the messaging system. This way, SOAP messages can be transmitted over an intranet using a reliable, asynchronous messaging system and over the global Internet (and through firewalls) using HTTP. One example of such an adapter is the Web Services Gateway for IBM's WebSphere Application Server. |
企业正在使用消息传递来使应用程序能够进行通信。然而,企业使用多个消息系统,这使得应用程序应该连接到哪个消息系统的问题变得混乱。
An enterprise is using Messaging to enable applications to communicate. However, the enterprise uses more than one messaging system, which confuses the issue of which messaging system an application should connect to.
|
如何连接多个消息传递系统,以便一个系统上可用的消息在其他系统上也可用? How can multiple messaging systems be connected so that messages available on one are also available on the others? |
一个常见问题是企业使用多个消息传递系统。发生这种情况的原因是两家不同的公司之间进行了合并或收购,而这两家公司已经针对不同的消息传递产品进行了标准化。有时,使用一个消息系统来集成其大型机/遗留系统的单个企业会选择另一个系统作为其 J2EE 或 .NET Web 应用程序服务器,然后需要集成这两个消息系统。另一种常见情况是作为多个企业一部分参与的应用程序,例如希望成为多个拍卖系统中的投标人的 B2B 客户。如果各个拍卖集群使用不同的消息系统,企业内的投标人应用程序可能希望将来自多个外部消息传送系统的消息合并到单个内部消息传送系统上。另一个例子是拥有大量人员的超大型企业。消息通道和消息端点可能
A common problem is an enterprise that uses more than one messaging system. This can occur because of a merger or acquisition between two different companies that have standardized around different messaging products. Sometimes a single enterprise that uses one messaging system to integrate its mainframe/legacy systems chooses another for its J2EE or .NET Web application servers and then needs to integrate the two messaging systems. Another common occurrence is an application that participates as part of multiple enterprises, such as a B2B client that wants to be a bidder in multiple auctioning systems. If the various auction clusters use different messaging systems, the bidder applications within an enterprise may wish to consolidate the messages from several external messaging systems onto a single internal messaging system. Another example is the extremely large enterprise with a huge number of Message Channels and Message Endpoints that may require more than one instance of the messaging system, which means those instances must be connected somehow.
如果一个系统上的消息对使用另一消息系统的应用程序不感兴趣,则这些系统可以保持完全独立。但由于这些应用程序是同一企业的一部分,因此使用一个消息传递系统的某些应用程序通常会对在另一消息传递系统上传输的消息感兴趣。
If the messages on one system are of no interest to the applications using the other messaging system, then the systems can remain completely separate. But because the applications are part of the same enterprise, often some applications using one messaging system will be interested in messages being transmitted on another messaging system.
一个常见的误解是 JMS 等标准化消息传递 API 可以解决这个问题;它不是。JMS 使两个兼容的消息传递系统对于客户端应用程序来说看起来相同,但它不会使这两个消息传递系统相互协作。为了使消息传递系统能够协同工作,它们需要具有互操作性,这意味着它们使用相同的消息格式并以相同的方式将消息从一个消息存储传输到下一个消息存储。来自两个不同供应商的消息系统很少可以互操作;来自一个供应商的消息存储只能与来自同一供应商的其他消息存储一起使用。
A common misconception is that a standardized messaging API such as JMS solves this problem; it does not. JMS makes two compliant messaging systems look the same to a client application, but it does nothing to make the two messaging systems work with each other. For the messaging systems to work together, they need to be interoperable, meaning that they use the same message format and transmit a message from one message store to the next in the same way. Messaging systems from two different vendors are rarely interoperable; a message store from one vendor can work only with other message stores from the same vendor.
企业中的每个应用程序都可以选择为企业中的每个消息传递系统实现一个客户端,但这会增加消息传递层的复杂性和重复性。如果企业添加了另一个消息系统并且所有应用程序都必须修改,那么这种冗余将变得尤其明显。另一方面,每个应用程序可以选择仅与一个消息传递系统交互并忽略其他消息传递系统上的数据。这将使应用程序更简单,但可能会导致它忽略大量企业数据。所需要的是一种使一个消息传送系统上的另一消息传送系统上的应用程序感兴趣的消息也可以在第二消息传送系统上可用的方式。
Each application in the enterprise could choose to implement a client for each messaging system in the enterprise, but that would increase complexity and duplication in the messaging layer. This redundancy would become especially apparent if the enterprise added yet another messaging system and all of the applications had to be modified. On the other hand, each application could choose to interface with only one messaging system and ignore data on the other messaging systems. This would make the application simpler but could cause it to ignore a great deal of enterprise data. What is needed is a way for messages on one messaging system that are of interest to applications on another messaging system to be made available on the second messaging system as well.
|
使用消息传递桥,消息传递系统之间的连接,用于在系统之间复制消息。 Use a Messaging Bridge, a connection between messaging systems that replicates messages between systems. |
通常,没有实际的方法来连接两个完整的消息系统,因此我们在消息系统之间连接单独的、相应的通道。消息传递桥是一组通道适配器,其中非消息传递客户端实际上是另一个消息传递系统,每对适配器连接一对相应的通道。桥充当从一组通道到另一组通道的映射,并将一个系统的消息格式转换为另一个系统。连接的通道可用于在消息传递系统的传统客户端之间传输消息,或者严格用于发送给其他消息传递系统的消息。
Typically, there is no practical way to connect two complete messaging systems, so instead we connect individual, corresponding channels between the messaging systems. The Messaging Bridge is a set of Channel Adapters, where the non-messaging client is actually another messaging system and where each pair of adapters connects a pair of corresponding channels. The bridge acts as a map from one set of channels to the other and transforms the message format of one system to the other. The connected channels may be used to transmit messages between traditional clients of the messaging system or strictly for messages intended for other messaging systems.
您可能需要为您的企业自行实施消息传递桥。该桥是一个专门的消息端点应用程序,它是两个消息系统的客户端。当消息在一个消息传递系统中的感兴趣的通道上传递时,桥接器会使用该消息并在另一个消息传递系统中的相应通道上发送具有相同内容的另一条消息。
You may need to implement the Messaging Bridge yourself for your enterprise. The bridge is a specialized Message Endpoint application that is a client of both messaging systems. When a message is delivered on a channel of interest in one messaging system, the bridge consumes the message and sends another with the same contents on the corresponding channel in the other messaging system.
许多消息传递系统供应商都有产品扩展,用于桥接其他供应商的消息传递系统。因此,您也许可以购买解决方案,而不是自己构建。
Many messaging system vendors have product extensions for bridging to messaging systems from other vendors. Thus, you may be able to buy a solution rather than build it yourself.
如果其他“消息系统”确实是一个更简单的协议,例如 HTTP,则应用通道适配器模式。
If the other "messaging system" is really a simpler protocol, such as HTTP, apply the Channel Adapter pattern.
消息传递桥是必要的,因为不同的消息传递系统实现对于如何表示消息以及如何将消息从一个存储转发到下一个存储有自己的专有方法。Web 服务可能会对此进行标准化,以便安装的两个消息系统(即使来自不同的供应商)也可以通过使用 Web 服务标准传输消息来充当一个系统。请参阅第 14 章“结束语”中对 WS-Reliability 和 WS-ReliableMessaging 的讨论。
Messaging Bridge is necessary because different messaging system implementations have their own proprietary approaches for how to represent messages and how to forward them from one store to the next. Web services may be standardizing this, such that two messaging system installs, even from different vendors, may be able to act as one by transferring messaging using Web services standards. See the discussion of WS-Reliability and WS-ReliableMessaging in Chapter 14, "Concluding Remarks."
|
示例: 股票交易 Example: Stock Trading 经纪公司可能有一个消息系统,其各个办事处的应用程序使用该系统进行通信。银行可能有不同的消息系统,其各个分支机构的应用程序使用该系统进行通信。如果经纪公司和银行决定合并为一家提供银行账户和投资服务的公司,合并后的公司应使用哪种消息系统?该公司可以使用消息桥来连接两个消息系统,而不是重新设计公司一半的应用程序以使用新的消息系统。这样,例如,银行应用程序和经纪应用程序可以协调在储蓄帐户和证券交易帐户之间转移资金。 A brokerage house may have one messaging system that the applications in its various offices use to communicate. A bank may have a different messaging system that the applications in its various branches use to communicate. If the brokerage and the bank decide to merge into a single company that offers bank accounts and investment services, which messaging system should the combined company use? Rather than redesigning half of the company's applications to use the new messaging system, the company can use a Messaging Bridge to connect the two messaging systems. This way, for example, a banking application and a brokerage application can coordinate to transfer money between a savings account and a securities trading account. |
|
示例: MSMQ 桥 Example: MSMQ Bridges MSMQ 定义了一种基于连接器服务器的体系结构,该体系结构使连接器应用程序能够使用其他(非 MSMQ)消息传递系统发送和接收消息。使用连接器服务器的 MSMQ 应用程序可以在来自其他消息传递系统的通道上执行与在 MSMQ 通道上执行的操作相同的操作 [ Dickman]。 MSMQ defines an architecture based on connector servers that enables connector applications to send and receive messages using other (non-MSMQ) messaging systems. An MSMQ application using a connector server can perform the same operations on channels from other messaging systems that it can perform on MSMQ channels [Dickman]. Microsoft 的主机集成服务器产品包含一个MSMQ-MQSeries 桥接服务,使两个消息系统能够协同工作。它允许 MSMQ 应用程序通过 MQSeries 通道发送消息,反之亦然,从而使两个消息系统充当一个整体。 Microsoft's Host Integration Server product contains an MSMQ-MQSeries Bridge service that makes the two messaging systems work together. It lets MSMQ applications send messages via MQSeries channels and vice versa, making the two messaging systems act as one. MSMQ-MQSeries Bridge 的许可方 Envoy Technologies 也有一款名为 Envoy Connect 的相关产品。它将 MSMQ 和 BizTalk 服务器与运行在非 Windows 平台(尤其是 J2EE 平台)上的消息传递服务器连接起来,协调企业内的 J2EE 和 .NET 消息传递。 Envoy Technologies, licenser of the MSMQ-MQSeries Bridge, also has a related product called Envoy Connect. It connects MSMQ and BizTalk servers with messaging servers running on non-Windows platforms, especially the J2EE platform, coordinating J2EE and .NET messaging within an enterprise. |
|
示例: SonicMQ 桥 Example: SonicMQ Bridges Sonic Software 的 SonicMQ 拥有支持 IBM MQSeries、TIBCO TIB/Rendezvous 和 JMS 的 SonicMQ Bridge 产品。这使得 Sonic 通道上的消息也可以在其他消息系统的通道上传输。 Sonic Software's SonicMQ has SonicMQ Bridge products that support IBM MQSeries, TIBCO TIB/Rendezvous, and JMS. This enables messages on Sonic channels to be transmitted on other messaging systems' channels as well. |
企业包含多个现有系统,这些系统必须能够共享数据并以统一的方式运行,以响应一组常见的业务请求。
An enterprise contains several existing systems that must be able to share data and operate in a unified manner in response to a set of common business requests.
|
什么架构能够使单独的应用程序以解耦的方式协同工作,以便可以轻松添加或删除应用程序而不影响其他应用程序? What architecture enables separate applications to work together but in a decoupled fashion such that applications can be easily added or removed without affecting the others? |
一个企业往往包含多种独立运行的应用程序,但又必须以统一的方式协同工作。企业应用程序集成 (EAI) 定义了此问题的解决方案,但没有描述如何实现它。
An enterprise often contains a variety of applications that operate independently but must work together in a unified manner. Enterprise Application Integration (EAI) defines a solution to this problem but doesn't describe how to accomplish it.
例如,考虑一家销售不同种类保险产品(人寿、健康、汽车、房屋等)的保险公司。由于企业合并和 IT 开发中不断变化的变革趋势,企业由许多独立的应用程序组成,用于管理公司的各种产品。试图向客户销售多种不同类型保单的保险代理人必须为每项保单登录到单独的系统,这不仅浪费精力,而且增加了出错的机会。
For example, consider an insurance company that sells different kinds of insurance products (life, health, auto, home, etc.). As a result of corporate mergers and of the varying winds of change in IT development, the enterprise consists of a number of separate applications for managing the company's various products. An insurance agent trying to sell a customer several different types of policies must log onto a separate system for each policy, wasting effort and increasing the opportunity for mistakes.
保险公司 EAI 场景
Insurance Company EAI Scenario
代理需要一个统一的应用程序来向客户销售保单组合。其他类型的保险公司员工,例如理赔员和客户服务代表,需要自己的应用程序来使用保险产品,但他们也希望自己的应用程序能够呈现统一的视图。各个产品应用程序必须能够协同工作,也许可以在购买多份保单时提供折扣,并处理多份保单涵盖的索赔。
The agent needs a single, unified application for selling customers a portfolio of policies. Other types of insurance company employees, such as claims adjusters and customer service representatives, need their own applications for working with the insurance products, but they also want their applications to present a unified view. The individual product applications must be able to work together, perhaps to offer a discount when purchasing more than one policy and to process a claim that is covered by more than one policy.
IT 部门可以重写产品应用程序,使其全部使用相同的技术并协同工作,但替换已经工作的系统(即使它们不能协同工作)所花费的时间和金钱却令人望而却步。IT 可以为代理创建统一的应用程序,但该应用程序需要连接到实际管理策略的系统。这一新应用程序并没有统一系统,而是创建了一个不与其他系统集成的系统。
The IT department could rewrite the product applications to all use the same technology and work together, but the amount of time and money to replace systems that already work (even though they don't work together) is prohibitive. IT could create a unified application for the agents, but this application needs to connect to the systems that actually manage the policies. Rather than unifying the systems, this new application creates one more system that doesn't integrate with the others.
代理应用程序可以与所有这些其他系统集成,但这会使其变得更加复杂。理赔员和客户服务代表的申请也会变得更加复杂。此外,这些统一的用户应用程序不利于产品应用程序之间的相互集成。
The agent application could integrate with all of these other systems, but that would make it much more complex. The complexity would be duplicated in the applications for claims adjusters and customer service representatives. Furthermore, these unified user applications would not help the product applications integrate with each other.
即使所有这些应用程序都可以协同工作,对企业配置的任何更改都可能使其全部停止工作。并非所有应用程序都始终可用,但正在运行的应用程序必须能够继续运行,并将未运行的应用程序的影响降至最低。随着时间的推移,需要在企业中添加和删除应用程序,同时对其他应用程序的影响最小。我们需要的是一种集成架构,使产品应用程序能够以松散耦合的方式进行协调,并使用户应用程序能够与它们集成。
Even if all of these applications could be made to work together, any change to the enterprise's configuration could make it all stop working. Not all applications will be available all of the time, yet the ones that are running must be able to continue with minimal impact from those that are not running. Over time, applications will need to be added to and removed from the enterprise, with minimal impact on the other applications. What is needed is an integration architecture that enables the product applications to coordinate in a loosely coupled way and for user applications to be able to integrate with them.
|
将这些应用程序之间的连接中间件构建为消息总线,使它们能够使用消息传递来协同工作。 Structure the connecting middleware between these applications as a Message Bus that enables them to work together using messaging. |
消息总线是规范数据模型、通用命令集和消息传递基础设施的组合,允许不同的系统通过一组共享接口进行通信。这类似于计算机系统中的通信总线,它充当 CPU、主存储器和外设之间通信的焦点。正如硬件类比一样,有许多部分组合在一起形成消息总线。
A Message Bus is a combination of a Canonical Data Model, a common command set, and a messaging infrastructure to allow different systems to communicate through a shared set of interfaces. This is analogous to a communications bus in a computer system, which serves as the focal point for communication between the CPU, main memory, and peripherals. Just as in the hardware analogy, there are a number of pieces that come together to form the message bus.
通用通信基础设施 正如PCI 总线的物理引脚和电线为 PC 提供了通用的、众所周知的物理基础设施一样,通用基础设施也必须在消息总线中服务于相同的目的。通常,选择消息系统作为物理通信基础设施,在应用程序之间提供跨平台、跨语言的通用适配器。该基础设施可以包括消息路由器功能,以促进消息在系统之间的正确路由。另一个常见的选项是使用发布-订阅通道来促进向所有接收者发送消息。
Common communication infrastructure Just as the physical pins and wires of a PCI bus provide a common, well-known physical infrastructure for a PC, a common infrastructure must serve the same purpose in a message bus. Typically, a messaging system is chosen to serve as the physical communications infrastructure, providing a crossplatform, cross-language universal adapter between the applications. The infrastructure may include Message Router capabilities to facilitate the correct routing of messages from system to system. Another common option is to use Publish-Subscribe Channels to facilitate sending messages to all receivers.
适配器不同的系统必须找到一种与消息总线接口的方法。某些应用程序可能已准备好连接到总线,但大多数应用程序需要适配器才能连接到消息传递系统。这些适配器通常是商业或定制的通道适配器和服务激活器。它们可能专门用于处理诸如使用适当的参数调用 CICS 事务或将总线的通用数据结构转换为应用程序使用的特定表示形式等任务。这还需要所有系统都同意的规范数据模型。
Adapters The different systems must find a way to interface with the Message Bus. Some applications may be ready-built to connect to the bus, but most will need adapters to connect to the messaging system. These adapters are commonly commercial or custom Channel Adapters and Service Activators. They may be specialized to handle tasks like invoking CICS transactions with the proper parameters or converting the bus's general data structures to the specific representation an application uses. This also requires a Canonical Data Model that all systems can agree on.
通用命令结构 正如PC 体系结构具有一组通用命令来表示物理总线上可能的不同操作(从地址读取字节、向地址写入字节)一样,消息总线中的所有参与者也必须有通用命令可以理解。命令消息说明了此功能的工作原理。另一个常见的实现是数据类型 Channel ,其中消息路由器明确决定如何将特定消息(如采购订单)路由到特定端点。最后,这个类比就不成立了,因为总线上承载的消息的级别比物理总线上承载的“读/写”类型的消息要细粒度得多。
Common command structure Just as PC architectures have a common set of commands to represent the different operations possible on the physical bus (read bytes from an address, write bytes to an address), there must be common commands that all the participants in the Message Bus can understand. Command Message illustrates how this feature works. Another common implementation for this is the Datatype Channel, where a Message Router makes an explicit decision as to how to route particular messages (like purchase orders) to particular endpoints. It is at the end that the analogy breaks down, since the level of the messages carried on the bus are much more fine-grained than the "read/write" kinds of messages carried on a physical bus.
在我们的 EAI 示例中,消息总线可以充当各种保险系统之间的通用连接器,以及希望连接到保险系统的客户端应用程序的通用接口。
In our EAI example, a Message Bus could serve as a universal connector between the various insurance systems and as a universal interface for client applications that wish to connect to the insurance systems.
保险公司消息总线
Insurance Company Message Bus
这里我们有两个只了解消息总线的 GUI ;他们完全不知道底层系统的复杂性。总线负责将命令消息路由到适当的底层系统。在某些情况下,处理命令消息的最佳方法是构建一个系统适配器,用于解释命令,然后以系统理解的方式与系统进行通信(例如,调用 CICS 事务,或调用 C++ API) 。在其他情况下,可以将命令处理逻辑直接构建到现有系统中,作为调用当前逻辑的附加方式。
Here we have two GUIs that know only about the Message Bus; they are entirely unaware of the complexities of the underlying systems. The bus is responsible for routing Command Messages to the proper underlying systems. In some cases, the best way to handle the command messages is to build an adapter to the system that interprets the command and then communicates with the system in a way it understands (invoking a CICS transaction, for instance, or calling a C++ API). In other cases, it may be possible to build the command-processing logic directly into the existing system as an additional way to invoke current logic.
一旦为代理 GUI 开发了消息总线,就可以轻松地为其他 GUI 重用,例如索赔处理者、客户服务代表以及使用 Web 界面浏览自己帐户的客户的 GUI。这些 GUI 应用程序的功能和安全控制各不相同,但它们与后端应用程序配合使用的需求是相同的。
Once the Message Bus has been developed for the agent GUI, it is easy to reuse for other GUIs, such as those for claims processors, customer service representatives, and customers who use a Web interface for to browse their own accounts. The features and security control of these GUI applications differ, but their need to work with the back-end applications is the same.
消息总线为企业形成了一个简单、有用的面向服务的体系结构。每个服务至少有一个接受商定格式请求的请求通道,并且可能有一个支持指定回复格式的相应回复通道。任何参与者应用程序都可以通过发出请求并等待回复来使用这些服务。实际上,请求通道充当可用服务的目录。
A Message Bus forms a simple, useful service-oriented architecture for an enterprise. Each service has at least one request channel that accepts requests of an agreed-upon format and probably a corresponding reply channel that supports a specified reply format. Any participant application can use these services by making requests and waiting for replies. The request channels, in effect, act as a directory of the services available.
消息总线要求所有使用该总线的应用程序都使用相同的规范数据模型。将消息添加到总线的应用程序可能需要依赖消息路由器将消息路由到适当的最终目的地。未设计为与消息传递系统交互的应用程序可能需要通道适配器和服务激活器。
A Message Bus requires that all of the applications using the bus use the same Canonical Data Model. Applications adding messages to the bus may need to depend on Message Routers to route the messages to the appropriate final destinations. Applications not designed to interface with a messaging system may require Channel Adapters and Service Activators.
|
示例: 股票交易 Example: Stock Trading 股票交易系统可能希望提供一套统一的服务,包括股票交易、债券拍卖、报价、投资组合管理等。这可能需要几个必须相互协调的独立后端系统。为了统一前端客户 GUI 的服务,系统可以采用中间应用程序来提供所有这些服务并将其性能委托给后端系统。后端系统甚至可以通过这个中间应用程序进行协调。然而,中间应用程序往往会成为瓶颈和单点故障。 A stock trading system may wish to offer a unified suite of services including stock trades, bond auctions, price quotes, portfolio management, and so on. This may require several separate back-end systems that have to coordinate with each other. To unify the services for a front-end customer GUI, the system could employ an intermediate application that offered all of these services and delegated their performance to the back-end systems. The back-end systems could even coordinate through this intermediary application. However, the intermediary application would tend to become a bottleneck and a single point of failure. 更好的方法可能是消息总线,它具有用于请求各种服务并获取响应的通道,而不是中间应用程序。该总线还可以使后端系统能够相互协调。前端系统可以简单地连接到总线并使用它来调用服务。总线可以相对容易地分布在多台计算机上,以提供负载分布和容错能力。 Rather than an intermediary application, a better approach might be a Message Bus with channels for requesting various services and getting responses. This bus could also enable back-end systems to coordinate with each other. A front-end system could simply connect to the bus and use it to invoke services. The bus could be distributed relatively easily across multiple computers to provide load distribution and fault tolerance. 一旦消息总线到位,连接前端 GUI 就会很容易;他们每个人只需要从适当的渠道发送和接收消息。一个 GUI 可以让零售经纪商管理其客户的投资组合。另一个基于 Web 的 GUI 可以让任何拥有 Web 浏览器的客户管理自己的产品组合。另一个非 GUI 前端可能支持个人财务程序,如 Intuit 的 Quicken 和 Microsoft 的 Money,使客户能够使用这些程序下载交易和当前价格。一旦消息总线就位,开发新的用户应用程序就会变得更加简单。 Once the Message Bus is in place, connecting front-end GUIs would be easy; they each just need to send and receive messages from the proper channels. One GUI might enable a retail broker to manage his customers' portfolios. Another Web-based GUI could enable any customer with a Web browser to manage his own portfolio. Another non-GUI front end might support personal finance programs like Intuit's Quicken and Microsoft's Money, enabling customers using those programs to download trades and current prices. Once the Message Bus is in place, developing new user applications is much simpler. 同样,交易系统可能希望利用新的后端应用程序,例如将一个交易应用程序切换为另一个交易应用程序或将报价请求分散到多个应用程序。实现这样的更改就像在消息总线中添加和删除应用程序一样简单。一旦新的应用程序就位,其他应用程序就不必更改;他们只是像往常一样继续在总线频道上发送消息。 Likewise, the trading system may want to take advantage of new back-end applications, such as by switching one trading application for another or spreading price quote requests across multiple applications. Implementing a change like this is as simple as adding and removing applications from the Message Bus. Once the new applications are in place, none of the other applications have to change; they just keep sending messages on the bus's channels as usual. |
在第 3 章“消息系统”中,我们讨论了Message 。当两个应用程序想要交换一段数据时,它们通过将其包装在消息中来实现。虽然消息通道本身无法传输原始数据,但它可以传输封装在消息中的数据。创建和发送消息会引发其他几个问题。
In Chapter 3, "Messaging Systems," we discussed Message. When two applications want to exchange a piece of data, they do so by wrapping it in a message. Whereas a Message Channel cannot transmit raw data per se, it can transmit the data wrapped in a message. Creating and sending a Message raises several other issues.
消息意图 消息最终只是数据束,但发送者可以对期望接收者对消息执行的操作有不同的意图。它可以发送命令消息,指定发送者希望调用的接收者上的函数或方法。发送者告诉接收者要运行什么代码。它可以发送文档消息,使发送者能够将其数据结构之一传输到接收者。发送者将数据传递给接收者,但没有指定接收者必须用它做什么。或者它可以发送事件消息,通知接收者发送者的变化。发送者并不告诉接收者如何反应,只是提供通知。
返回响应 当应用程序发送消息时,它通常期望一个响应来确认该消息已被处理并提供结果。这是一个请求-回复场景。请求通常是命令消息,回复是包含结果值或异常的文档消息。请求者应在请求中指定返回地址,以告诉回复者使用哪个通道来传输回复。请求者可能有多个请求正在处理中,因此回复应包含一个相关标识符,指定此回复对应于哪个请求。
有两种常见的请求-回复场景值得注意;两者都涉及命令消息请求和相应的文档消息回复。在第一个场景(消息 RPC)中,请求者不仅想要调用回复者上的函数,而且还想要函数的返回值。这就是应用程序使用消息传递执行 RPC(远程过程调用)的方式。 另一种场景Messaging Query,请求者执行查询;回复者执行查询并在回复中返回结果。这就是应用程序如何使用消息传递来远程执行查询。
海量数据 有时,应用程序想要传输非常大的数据结构,该数据结构可能无法轻松地容纳在单个消息中。在这种情况下,将数据分成更易于管理的块并将它们作为消息序列发送。块必须作为序列发送,而不仅仅是一堆消息,以便接收者可以重建原始数据结构。
消息缓慢 消息传递的一个问题是发送者通常不知道接收者需要多长时间才能收到消息。然而,消息内容可能是时间敏感的,因此如果在特定期限内未收到消息,则应忽略并丢弃该消息。在这种情况下,发送者可以使用消息过期来指定过期日期。如果消息传递系统无法在过期前传递消息,则应丢弃该消息或将其移至死信通道。同样,如果接收方在过期后收到消息,则应丢弃该消息。
Message intent Messages are ultimately just bundles of data, but the sender can have different intentions for what it expects the receiver to do with the message. It can send a Command Message, specifying a function or method on the receiver that the sender wishes to invoke. The sender is telling the receiver what code to run. It can send a Document Message, enabling the sender to transmit one of its data structures to the receiver. The sender is passing the data to the receiver but not specifying what the receiver should necessarily do with it. Or it can send an Event Message, notifying the receiver of a change in the sender. The sender is not telling the receiver how to react, just providing notification.
Returning a response When an application sends a message, it often expects a response confirming that the message has been processed and providing the result. This is a Request-Reply scenario. The request is usually a Command Message, and the reply is a Document Message containing a result value or an exception. The requestor should specify a Return Address in the request to tell the replier what channel to use to transmit the reply. The requestor may have multiple requests in process, so the reply should contain a Correlation Identifier that specifies which request this reply corresponds to.
There are two common Request-Reply scenarios worth noting; both involve a Command Message request and a corresponding Document Message reply. In the first scenario, Messaging RPC, the requestor not only wants to invoke a function on the replier, but also wants the return value from the function. This is how applications perform an RPC (Remote Procedure Call) using Messaging. In the other scenario, Messaging Query, the requestor performs a query; the replier executes the query and returns the results in the reply. This is how applications use messaging to perform a query remotely.
Huge amounts of data Sometimes applications want to transfer a really large data structure, one that may not fit comfortably in a single message. In this case, break the data into more manageable chunks and send them as a Message Sequence. The chunks must be sent as a sequence, not just a bunch of messages, so the receiver can reconstruct the original data structure.
Slow messages A concern with messaging is that the sender often does not know how long it will take for the receiver to receive the message. Yet, the message contents may be time-sensitive, so if the message isn't received by a certain deadline, it should be ignored and discarded. In this situation, the sender can use Message Expiration to specify an expiration date. If the messaging system cannot deliver a message by its expiration, it should discard the message or move it to a Dead Letter Channel. Likewise, if a receiver gets a message after its expiration, it should discard the message.
总之,仅仅选择使用消息是不够的。当必须传输数据时,必须通过 Message 来完成。 本章解释了使消息发挥作用的其他决策。
In summary, simply choosing to use a Message is insufficient. When data must be transferred, it must be done through a Message. This chapter explains other decisions that are part of making messages work.
应用程序需要调用其他应用程序提供的功能。它通常会使用远程过程调用,但它希望利用使用消息传递。
An application needs to invoke functionality provided by other applications. It would typically use Remote Procedure Invocation, but it would like to take advantage of the benefits of using Messaging.
|
如何使用消息传递来调用另一个应用程序中的过程? How can messaging be used to invoke a procedure in another application? |
远程过程调用的优点是它是同步的,因此当调用者的线程阻塞时,调用会立即执行。但这也是一个缺点。如果由于网络故障或远程进程未运行和侦听而无法立即执行调用,则调用不起作用。如果调用是异步的,它可以继续尝试,直到成功调用远程应用程序中的过程。
The advantage of Remote Procedure Invocation is that it's synchronous, so the call is performed immediately while the caller's thread blocks. But that's also a disadvantage. If the call cannot be executed immediatelyeither because the network is down or because the remote process isn't running and listeningthen the call doesn't work. If the call were asynchronous, it could keep trying until the procedure in the remote application is successfully invoked.
本地调用比远程调用更可靠。如果调用者可以将过程的调用作为 Message 传输到接收者,则接收者可以在本地执行该调用。所以,问题是如何将过程的调用变成消息。
A local invocation is more reliable than a remote invocation. If the caller could transmit the procedure's invocation to the receiver as a Message, then the receiver could execute the invocation locally. So, the question is how to make a procedure's invocation into a message.
有一个完善的模式可以将请求封装为对象。命令模式[ GoF]展示了如何将请求转换为可以存储和传递的对象。如果该对象是一条消息,那么它可以存储在 Message Channel 中并通过Message Channel 传递。同样,命令的状态(例如方法参数)可以存储在消息的状态中。
There's a well-established pattern for encapsulating a request as an object. The Command pattern [GoF] shows how to turn a request into an object that can be stored and passed around. If this object were a message, then it could be stored in and passed around through a Message Channel. Likewise, the command's state (such as method parameters) can be stored in the message's state.
|
使用命令消息可靠地调用另一个应用程序中的过程。 Use a Command Message to reliably invoke a procedure in another application. |
命令没有特定的消息类型;命令消息只是碰巧包含命令的常规消息。在JMS中,命令消息可以是任何类型的消息;示例包括包含可序列化命令对象ObjectMessage、包含 XML 形式的命令TextMessage 等。在 .NET 中,命令消息是其中存储有命令的消息。简单对象访问协议 (Simple Object Access Protocol, ) 请求是一条命令消息。
There is no specific message type for commands; a Command Message is simply a regular message that happens to contain a command. In JMS, the command message could be any type of message; examples include an ObjectMessage containing a Serializable command object, a TextMessage containing the command in XML form, and so on. In .NET, a command message is a Message with a command stored in it. A Simple Object Access Protocol ( ) request is a command message.
命令消息通常通过点对点,以便每个命令仅被使用和调用一次。
Command Messages are usually sent over a Point-to-Point Channel so that each command will be consumed and invoked only once.
|
示例: SOAP 和 WSDL Example: SOAP and WSDL 根据 SOAP 协议 [ SOAP 1.1 ] 和 WSDL 服务描述 [ WSDL 1.1 ],当使用 RPC 样式的 SOAP 消息时,请求消息就是此命令消息模式的一个示例。通过这种用法,SOAP 消息正文(XML 文档)包含要在接收器中调用的方法的名称以及要传递到该方法中的参数值。此方法名称必须与接收方 WSDL 中定义的消息名称之一相同。 With the SOAP protocol [SOAP 1.1] and WSDL service description [WSDL 1.1], when using RPC-style SOAP messages, the request message is an example of this Command Message pattern. With this usage, the SOAP message body (an XML document) contains the name of the method to invoke in the receiver and the parameter values to pass into the method. This method name must be the same as one of the message names defined in the receiver's WSDL. SOAP 规范中的此示例使用名为 symbol 的单个参数调用接收者的GetLastTradePrice方法。 This example from the SOAP specification invokes the receiver's GetLastTradePrice method with a single parameter called symbol. <SOAP-ENV:信封
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:正文>
<m:GetLastTradePrice xmlns:m="Some-URI">
<符号>DIS</符号>
</m:获取最后交易价格>
</SOAP-ENV:正文>
</SOAP-ENV:信封>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<m:GetLastTradePrice xmlns:m="Some-URI">
<symbol>DIS</symbol>
</m:GetLastTradePrice>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
在 SOAP 命令中,人们可能期望方法名称是某个标准<method>元素的值;实际上,方法名称是方法元素的名称,以m命名空间为前缀。为每个方法提供单独的 XML 元素类型可以使 XML 数据的验证更加精确,因为方法元素类型可以指定参数的名称、类型和顺序。 In a SOAP command, one might expect the method name to be the value of some standard <method> element; actually, the method name is the name of the method element, prefixed by the m namespace. Having a separate XML element type for each method makes validating the XML data much more precise, because the method element type can specify the parameters' names, types, and order. |
一个应用程序想要将数据传输到另一个应用程序。它可以使用文件传输或共享数据库来做到这一点,但这些方法都有缺点。使用消息传递可能会更好地进行传输。
An application would like to transfer data to another application. It could do so using File Transfer or Shared Database, but those approaches have shortcomings. The transfer might work better using Messaging.
|
如何使用消息传递在应用程序之间传输数据? How can Messaging be used to transfer data between applications? |
这是分布式处理中的一个经典问题:一个进程拥有另一个进程需要的数据。文件传输很容易使用,但它不能很好地协调应用程序。一个应用程序写入的文件可能会在另一应用程序读取它之前很长一段时间未使用。如果多个应用程序要读取它,则不清楚谁应该负责删除它。
This is a classic problem in distributed processing: One process has data that another one needs. File Transfer is easy to use, but it doesn't coordinate applications very well. A file written by one application may sit unused for quite a while before another application reads it. If several applications are supposed to read it, it'll be unclear who should take responsibility for deleting it.
共享数据库需要一旦数据进入数据库,就存在其他不应访问该数据的应用程序现在可以访问该数据的风险。触发数据接收者来读取数据可能很困难,并且协调多个读取器可能会造成谁应该删除数据的混乱。
Shared Database requires adding new schema to the database to accommodate the data or force-fitting the data into the existing schema. Once the data is in the database, there's the risk that other applications that should not have access to the data now do. Triggering the receiver of the data to come and read it can be difficult, and coordinating multiple readers can create confusion about who should delete the data.
远程过程调用可用于发送数据,但调用者还通过被调用的过程告诉接收者如何处理数据。同样,命令消息将此外,远程过程调用
Remote Procedure Invocation can be used to send the data, but then the caller is also telling the receivervia the procedure being invokedwhat to do with the data. Likewise, a Command Message would transfer the data but would be overly specific about what the receiver should do with the data. Also, Remote Procedure Invocation assumes two-way communication, which is unnecessary if we only want to pass data from one application to another.
然而,我们确实想使用消息传递来传输数据。消息传递比 RPC 更可靠。点对点通道可用于确保只有一个接收者获得数据(无重复),或者发布-订阅通道可用于确保任何想要该数据的接收者都能获得数据的副本。因此,诀窍是利用消息传递的优势,但又不能让消息变得太像 RPC。
Yet, we do want to use Messaging to transfer the data. Messaging is more reliable than an RPC. A Point-to-Point Channel can be used to make sure that only one receiver gets the data (no duplication), or a Publish-Subscribe Channel can be used to make sure that any receiver who wants the data gets a copy of it. So, the trick is to take advantage of Messaging without making the Message too much like an RPC.
|
使用文档消息在应用程序之间可靠地传输数据结构。 Use a Document Message to reliably transfer a data structure between applications. |
命令消息告诉接收者调用某些行为,而文档消息只是传递数据并让接收者决定如何处理数据(如果有的话)。数据是单个数据单元、单个对象或可以分解为更小的单元的数据结构。
Whereas a Command Message tells the receiver to invoke certain behavior, a Document Message just passes data and lets the receiver decide what, if anything, to do with the data. The data is a single unit of data, a single object or data structure that may decompose into smaller units.
文档消息看起来非常像事件消息;主要区别在于时间和内容的问题。文档消息的重要部分是其内容:文档。成功传输文件很重要;发送和接收的时间不太重要。保证交付可能是一个考虑因素;消息过期可能不是。相反,事件消息的存在和时机通常比其内容更重要。
Document Messages can seem very much like Event Messages; the main difference is a matter of timing and content. The important part of a Document Message is its content: the document. Successfully transferring the document is important; the timing of when it is sent and received is less important. Guaranteed Delivery may be a consideration; Message Expiration probably is not. In contrast, an Event Messages existence and timing are often more important than its content.
文档消息可以是消息传递系统中的任何类型的消息。在 JMS 中,文档消息可以是包含文档的可序列化数据对象的ObjectMessage ,也可以是包含 XML 形式的数据TextMessage 。在 .NET 中,文档消息是其中存储有数据的消息。简单对象访问协议 (SOAP) 回复消息是文档消息。
A Document Message can be any kind of message in the messaging system. In JMS, the document message may be an ObjectMessage containing a Serializable data object for the document, or it may be a TextMessage containing the data in XML form. In .NET, a document message is a Message with the data stored in it. A Simple Object Access Protocol (SOAP) reply message is a document message.
文档消息通常使用点对点以将文档从一个进程移动到另一个进程而不进行复制。消息传递可用于实现简单的工作流程,方法是将文档传递到应用程序,应用程序修改该文档,然后将其传递到另一个应用程序。在某些情况下,可以通过发布-订阅通道广播文档消息,但这会创建文档的多个副本。副本必须是只读的;否则,如果接收者更改副本,系统中将出现该文档的多个副本,每个副本包含不同的数据。在Request-Reply中,回复通常是文档消息 ,其中结果值是文档。
Document Messages are usually sent using a Point-to-Point Channel to move the document from one process to another without duplicating it. Messaging can be used to implement simple workflow by passing a document to an application that modifies the document and then passes it to another application. In some cases, a document message can be broadcast via a Publish-Subscribe Channel, but this creates multiple copies of the document. The copies need to be read-only; otherwise, if the receivers change the copies, there will be multiple copies of the document in the system, each containing different data. In Request-Reply, the reply is usually a Document Message, where the result value is the document.
|
示例: Java 和 XML Example: Java and XML 以下示例(取自 [ Graham ] 中的示例 XML 模式)显示了如何将简单的采购订单表示为 XML 并使用 JMS 作为消息发送。 The following example (drawn from the example XML schema in [Graham]) shows how a simple purchase order can be represented as XML and sent as a message using JMS. Session session = // 获取会话 Destination dest = // 获取目的地 MessageProducer 发送者 = session.createProducer(dest); 字符串购买订单 = " <po id=\"48881\" 提交=\"2002-04-23\"> <收货地址> <公司>巧克力狂</公司> <街道>北街2112号</街道> <城市>卡里</城市> <州>北卡罗来纳州</州> <邮政编码>27522</邮政编码> </发货> <订单> <商品 sku=\"22211\" 数量=\"40\"> <描述>兔子,黑巧克力,大号< Session session = // Obtain the session Destination dest = // Obtain the destination MessageProducer sender = session.createProducer(dest); String purchaseOrder = " <po id=\"48881\" submitted=\"2002-04-23\"> <shipTo> <company>Chocoholics</company> <street>2112 North Street</street> <city>Cary</city> <state>NC</state> <postalCode>27522</postalCode> </shipTo> <order> <item sku=\"22211\" quantity=\"40\"> <description>Bunny, Dark Chocolate, Large< /description> </item> </order> </po>"; TextMessage message = session.createTextMessage(); message.setText(purchaseOrder); sender.send(message); |
|
示例: SOAP 和 WSDL Example: SOAP and WSDL 根据 SOAP 协议 [ SOAP 1.1 ] 和 WSDL 服务描述 [ WSDL 1.1 ],当使用文档样式的 SOAP 消息时,SOAP 消息是文档消息的一个示例。SOAP 消息主体是 XML 文档(或已转换为 XML 文档的某种数据结构),并且 SOAP 消息将该文档从发送者(例如,客户端)传输到接收者(例如,服务器) 。 With the SOAP protocol [SOAP 1.1] and WSDL service description [WSDL 1.1], when using document-style SOAP messages, the SOAP message is an example of a Document Message. The SOAP message body is an XML document (or some kind of data structure that has been converted into an XML document), and the SOAP message transmits that document from the sender (e.g., the client) to the receiver (e.g., the server). 当使用 RPC 样式的 SOAP 消息传递时,响应消息就是此模式的一个示例。通过这种用法,SOAP 消息正文(XML 文档)包含所调用方法的返回值。SOAP 规范中的此示例通过调用GetLastTradePrice方法返回答案。 When using RPC-style SOAP messaging, the response message is an example of this pattern. With this usage, the SOAP message body (an XML document) contains the return value from the method that was invoked. This example from the SOAP specification returns the answer from invoking the GetLastTradePrice method. <SOAP-ENV:信封 xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap /encoding/"/> <SOAP-ENV:Body> <m:GetLastTradePriceResponse xmlns:m="Some-URI"> <Price>34.5</Price> </m:GetLastTradePriceResponse> </SOAP-ENV:Body> </SOAP-ENV:Envelope> |
一些应用程序希望使用事件通知来协调它们的操作,并希望使用消息传递来传达这些事件。
Several applications would like to use event notification to coordinate their actions and would like to use Messaging to communicate those events.
|
如何使用消息传递将事件从一个应用程序传输到另一个应用程序? How can messaging be used to transmit events from one application to another? |
有时,一个对象中发生了另一个对象需要了解的事件。典型的例子是模型-视图-控制器架构[ POSA ],其中模型更改其状态并且必须通知其视图以便它们可以重新绘制自己。这种更改通知在分布式系统中也很有用。例如,在 B2B 系统中,一个企业可能需要通知其他企业价格变化或全新产品目录。
Sometimes, an event occurs in one object that another object needs to know about. The classic example is a Model-View-Controller architecture [POSA], where the model changes its state and must notify its views so that they can redraw themselves. Such change notification can also be useful in distributed systems. For example, in a B2B system, one business may need to notify others of price changes or a whole new product catalog.
进程可以使用远程过程调用来通知其他应用程序更改事件,但这要求接收者立即接受事件,即使它现在不需要事件。RPC 还要求通告进程知道每个侦听器进程并在每个侦听器上调用 RPC。
A process can use Remote Procedure Invocation to notify other applications of change events, but that requires that the receiver accept the event immediately, even if it doesn't want events right now. RPC also requires that the announcing process know every listener process and invoke an RPC on each listener.
观察者模式[ GoF]描述了如何设计一个宣布事件的主题和消费事件的观察者。主体通过调用观察者的Update()方法向观察者通知事件。Update()可以作为 RPC 实现,但它具有 RPC 的所有缺点。
The Observer pattern [GoF] describes how to design a subject that announces events and observers that consume events. A subject notifies an observer of an event by calling the observer's Update() method. Update() can be implemented as an RPC, but it would have all of RPC's shortcomings.
最好以Message 的形式异步发送事件通知。这样,主体可以在准备好时发送通知,并且每个观察者都可以在准备好时接收通知。
It would be better to send the event notification asynchronously, as a Message. This way, the subject can send the notification when it's ready, and each observer can receive the notification if and when it's ready.
|
使用事件消息在应用程序之间提供可靠的异步事件通知。 Use an Event Message for reliable, asynchronous event notification between applications. |
当主题有一个事件要宣布时,它会创建一个事件对象,将其包装在消息中,并将其作为事件消息发送到通道上。 观察者接收事件消息,获取事件并处理它。消息传递不会更改正在宣布的事件,而只是确保通知到达观察者。
When a subject has an event to announce, it creates an event object, wraps it in a message, and sends it on a channel as an Event Message. The observer receives the Event Message, gets the event, and processes it. Messaging does not change the event that's being announced, but just makes sure that the notification gets to the observer.
事件消息可以是消息传递系统中的任何类型的消息。在 Java 中,事件可以是对象或数据,例如 XML 文档。因此,它可以通过 JMS 作为ObjectMessage 、 TextMessage等进行传输。在 .NET 中,事件消息是存储有事件的消息。
An Event Message can be any kind of message in the messaging system. In Java, an event can be an object or data, such as an XML document. Thus, it can be transmitted through JMS as an ObjectMessage, TextMessage, and so on. In .NET, an event message is a Message with the event stored in it.
事件消息和文档消息之间的区别在于时间和内容的问题。事件的内容通常不太重要。许多事件甚至有一个空的消息正文;它们的出现就足以让观察者做出反应。活动的时机非常重要;一旦发生变化,主体应该立即发出一个事件,而观察者应该在事件仍然相关时快速处理它。保证交付通常对事件没有太大帮助,因为事件频繁且需要快速交付。消息过期对于确保快速处理事件或根本不处理事件非常有帮助。
The difference between an Event Message and a Document Message is a matter of timing and content. An event's contents are typically less important. Many events even have an empty message body; their mere occurrence tells the observer to react. An event's timing is very important; the subject should issue an event as soon as a change occurs, and the observer should process it quickly while it's still relevant. Guaranteed Delivery is usually not very helpful with events, because they're frequent and need to be delivered quickly. Message Expiration can be very helpful to make sure that an event is processed quickly or not at all.
例如,必须通知其他企业价格或产品变化的 B2B 系统可以使用事件消息、文档消息或两者的组合。如果一条消息表明计算机磁盘驱动器的价格已发生变化,则这是一个事件。如果该消息提供有关磁盘驱动器的信息(包括其新价格),则该消息是作为事件发送的文档。宣布新目录及其 URL 的另一条消息是一个事件,而实际包含新目录的类似消息是一个包含文档的事件。
The B2B system that must notify other businesses of price or product changes, for example, could use Event Messages, Document Messages, or a combination of the two. If a message says that the price for computer disk drives has changed, that's an event. If the message provides information about the disk drive, including its new price, that's a document being sent as an event. Another message that announces the new catalog and its URL is an event, whereas a similar message that actually contains the new catalog is an event that contains a document.
哪个更好?观察者模式将其描述为推模型和拉模型之间的权衡。推送模型发送有关更改的信息作为更新的一部分,而拉取模型发送最少的信息,需要更多信息的观察者通过向主题发送GetState()来请求它。这两个模型与这样的消息传递相关。
Which is better? The Observer pattern describes this as a trade-off between a push model and a pull model. The push model sends information about the change as part of the update, whereas the pull model sends minimal information, and observers that want more information request it by sending GetState() to the subject. The two models relate to messaging like this.
推送模型 消息是组合的文档/事件消息;消息的传递宣告状态已经发生并且消息的内容是新的状态。如果所有观察者都想要这些细节,这会更有效,但否则可能会是两全其美的情况:频繁发送的大消息常常被许多观察者忽略。
拉模型 有三个消息:
Push model The message is a combined document/event message; the message's delivery announces that the state has occurred and the message's contents are the new state. This is more efficient if all observers want these details, but otherwise it can be the worst of both worlds: a large message that is sent frequently and often ignored by many observers.
Pull model There are three messages:
Update is an Event Message that notifies the observer of the event.
State Request is a Command Message an interested observer uses to request details from the subject.
State Reply is a Document Message the subject uses to send the details to the observer.
拉模型的优点是更新消息很小,只有感兴趣的观察者请求详细信息,并且每个感兴趣的观察者都可以请求它特别感兴趣的详细信息。缺点是需要额外的通道数量以及由此产生的流量不止一条消息。
The advantages of the pull model are that the update messages are small, only interested observers request details, and potentially each interested observer can request the details it specifically is interested in. The disadvantage is the additional number of channels needed and the resulting traffic caused by more than one message.
有关如何使用消息传递实现Observer的更多详细信息,请参阅第 6 章“插曲:简单消息传递”中的“JMS 发布/订阅示例”部分。
For more details on how to implement Observer using messaging, see the section "JMS Publish/Subscribe Example" in Chapter 6, "Interlude: Simple Messaging."
通常没有理由将事件消息限制为通过点对点通道发送给单个接收者;该消息通常通过发布-订阅通道广播,以便所有感兴趣的进程都收到通知。虽然文档消息需要被消耗以便文档不丢失,但事件消息的接收者通常会在太忙而无法处理消息时忽略这些消息,因此订阅者通常可能是非持久的(不是持久订阅者) 。事件消息是使用消息传递实现观察者。
There is usually no reason to limit an event message to a single receiver via a Point-to-Point Channel; the message is usually broadcast via a Publish-Subscribe Channel so that all interested processes receive notification. Whereas a Document Message needs to be consumed so that the document is not lost, a receiver of Event Messages can often ignore the messages when it's too busy to process them, so the subscribers can often be nondurable (not Durable Subscribers). Event Message is a key part of implementing the Observer pattern using messaging.
当两个应用程序通过消息传递进行通信时,该通信是单向的。应用程序可能需要双向对话。
When two applications communicate via Messaging, the communication is one-way. The applications may want a two-way conversation.
|
当应用程序发送消息时,如何从接收者那里得到响应? When an application sends a message, how can it get a response from the receiver? |
消息传递提供消息在消息通道上沿一个传播;它们从发送者传输到接收者。这种异步传输使传送更加可靠,并将发送者与接收者解耦。
Messaging provides one-way communication between applications. Messages travel on a Message Channel in one direction; they travel from the sender to the receiver. This asynchronous transmission makes the delivery more reliable and decouples the sender from the receiver.
问题在于组件之间的通信通常需要是双向的。当程序调用函数时,它会收到一个返回值。当它执行查询时,它会接收查询结果。当一个组件通知另一个组件发生更改时,它可能希望收到确认。消息传递如何能够是双向的?
The problem is that communication between components often needs to be two-way. When a program calls a function, it receives a return value. When it executes a query, it receives query results. When one component notifies another of a change, it may want to receive an acknowledgment. How can messaging be two-way?
也许发送者和接收者可以同时共享消息。然后,每个应用程序都可以将信息添加到消息中以供其他应用程序使用。但这不是消息传递的工作原理。消息是先发送后接收的,因此发送者和接收者不能同时访问该消息。
Perhaps a sender and receiver could share a message simultaneously. Then, each application could add information to the message for the other to consume. But that is not how messaging works. A message is first sent and then received, so the sender and receiver cannot both access the message at the same time.
也许发件人可以保留对该消息的引用。然后,一旦接收者将其响应放入消息中,发送者就可以拉回消息。这可能适用于夹在晾衣绳上的笔记,但这不是消息通道的工作方式。通道在一个方向上传输消息。所需要的是双向通道上的双向消息。
Perhaps the sender could keep a reference to the message. Then, once the receiver placed its response into the message, the sender could pull the message back. This may work for notes clipped to a clothesline, but it is not how a Message Channel works. A channel transmits messages in one direction. What is needed is a two-way message on a two-way channel.
|
发送一对请求-答复消息,每个消息都在其自己的通道上。 Send a pair of Request-Reply messages, each on its own channel. |
请求-回复有两个参与者:
Request-Reply has two participants:
请求者发送请求消息并等待回复消息。
Requestor sends a request message and waits for a reply message.
回复者接收请求消息并用回复消息进行响应。
Replier receives the request message and responds with a reply message.
请求通道可以是点对点通道或发布订阅通道。区别在于请求是否应该广播给所有感兴趣的各方,或者应该仅由单个消费者处理。另一方面,回复通道几乎总是点对点的,因为广播回复通常没有意义——它们应该只返回给请求者。
The request channel can be a Point-to-Point Channel or a Publish-Subscribe Channel. The difference is whether the request should be broadcasted to all interested parties or should be processed by only a single consumer. The reply channel, on the other hand, is almost always point-to-point, because it usually makes no sense to broadcast repliesthey should be returned only to the requestor.
当调用者执行远程过程调用时,调用者的线程在等待响应时必须阻塞。通过Request-Reply,请求者有两种接收回复的方法。
When a caller performs a Remote Procedure Invocation, the caller's thread must block while it waits for the response. With Request-Reply, the requestor has two approaches for receiving the reply.
同步块 调用者中的单个线程发送请求消息,阻塞(作为轮询消费者)等待回复消息,然后处理回复。这实现起来很简单,但如果请求者崩溃,则很难重新建立被阻塞的线程。等待响应的请求线程意味着只有一个未完成的请求,或者该请求的回复通道对于该线程是私有的。
Synchronous Block A single thread in the caller sends the request message, blocks (as a Polling Consumer) to wait for the reply message, and then processes the reply. This is simple to implement, but if the requestor crashes, it will have difficulty reestablishing the blocked thread. The request thread awaiting the response implies that there is only one outstanding request or that the reply channel for this request is private for this thread.
异步回调 调用者中的 一个线程发送请求消息并为回复设置回调。一个单独的线程侦听回复消息。当回复消息到达时,回复线程调用适当的回调,该回调重新建立调用者的上下文并处理回复。这种方法允许多个未完成的请求共享单个回复通道和单个回复线程来处理多个请求线程的回复。如果请求者崩溃,只需重新启动回复线程即可恢复。然而,一个额外的复杂性是回调机制必须重新建立调用者的上下文。
Asynchronous Callback One thread in the caller sends the request message and sets up a callback for the reply. A separate thread listens for reply messages. When a reply message arrives, the reply thread invokes the appropriate callback, which reestablishes the caller's context and processes the reply. This approach enables multiple outstanding requests to share a single reply channel and a single reply thread to process replies for multiple request threads. If the requestor crashes, it can recover by simply restarting the reply thread. An added complexity, however, is the callback mechanism that must reestablish the caller's context.
两个应用程序相互发送请求和回复并不是很有帮助。有趣的是这两条消息代表什么。
Two applications sending requests and replies to each other are not very helpful. What is interesting is what the two messages represent.
消息传递 RPC这是使用消息传递 实现远程过程调用的方法。该请求是一条命令消息,描述了应答者应调用的功能。回复是包含函数的返回值或异常的文档消息。
Messaging RPC This is how to implement Remote Procedure Invocation using messaging. The request is a Command Message that describes the function the replier should invoke. The reply is a Document Message that contains the function's return value or exception.
消息传递查询 这是使用消息传递执行远程查询的方法。请求是包含查询的命令消息,回复是查询的结果,可能是消息序列。
Messaging Query This is how to perform a remote query using messaging. The request is a Command Message containing the query, and the reply is the results of the query, perhaps a Message Sequence.
通知/确认 这 提供了使用消息传递的事件通知和确认。请求是提供通知的事件消息,回复是确认通知的文档消息。该确认本身可能是另一项请求,即寻求有关事件的详细信息的请求。
Notify/Acknowledge This provides for event notification with acknowledgment, using messaging. The request is an Event Message that provides notification, and the reply is a Document Message acknowledging the notification. The acknowledgment may itself be another request, one seeking details about the event.
该请求就像一个方法调用。因此,答复是三种可能性之一:
The request is like a method call. As such, the reply is one of three possibilities:
Void 只是通知调用者该方法已完成,以便调用者可以继续。
Void Simply notifies the caller that the method has finished so that the caller can proceed.
结果值 作为方法的返回值的单个对象。
Result value A single object that is the method's return value.
Exception 单个异常对象,指示该方法在成功完成之前中止,并指示原因。
Exception A single exception object indicating that the method aborted before completing successfully, and indicating why.
请求应包含返回地址,以告诉回复者将回复发送到何处。回复应包含一个相关标识符,指定此回复针对哪个请求。
The request should contain a Return Address to tell the replier where to send the reply. The reply should contain a Correlation Identifier that specifies which request this reply is for.
|
示例: SOAP 1.1 消息 Example: SOAP 1.1 Messages SOAP 消息以请求-答复对的形式出现。SOAP 请求消息指示发送者想要在接收者上调用的服务,而 SOAP 响应消息则包含服务调用的结果。响应消息包含结果值或错误(相当于异常的 SOAP [ SOAP 1.1 ])。 SOAP messages come in Request-Reply pairs. A SOAP request message indicates a service the sender wants to invoke on the receiver, whereas a SOAP response message contains the result of the service invocation. The response message contains either a result value or a faultthe SOAP equivalent of an exception [SOAP 1.1]. |
|
示例: SOAP 1.2 响应消息交换模式 Example: SOAP 1.2 Response Message Exchange Pattern SOAP 1.1 松散地描述了响应消息,而 SOAP 1.2 引入了显式请求-响应消息交换模式[ SOAP 1.2 第 2 部分 ]。该模式描述了对 SOAP 请求的单独的、可能异步的响应。 Whereas SOAP 1.1 has loosely described response messages, SOAP 1.2 introduces an explicit Request-Response Message Exchange pattern [SOAP 1.2 Part 2]. This pattern describes a separate, potentially asynchronous response to a SOAP request. |
|
示例: JMS 请求者对象 Example: JMS Requestor Objects JMS 包含一些可用于实现Request-Reply 的功能。 JMS includes a couple of features that can be used to implement Request-Reply. TemporaryQueue是可以通过编程方式创建的队列,并且其持续时间与用于创建它的连接一样长。只有由同一连接创建的MessageConsumers才能从队列中读取数据,因此它对于连接来说实际上是私有的 [JMS 1.1] 。 A TemporaryQueue is a Queue that can be created programmatically and that lasts only as long as the Connection used to create it. Only MessageConsumers created by the same connection can read from the queue, so it is effectively private to the connection [JMS 1.1]. MessageProducers如何知道这个新创建的私有队列?请求者创建一个临时队列并在请求消息的reply-to属性中指定它(请参阅返回地址)。行为良好的回复者会将回复发送回指定的队列,如果它不是请求消息的属性,回复者甚至不会知道该队列。这是请求者可以使用的一种简单方法,以确保始终得到回复。 How do MessageProducers know about this newly created, private queue? A requestor creates a temporary queue and specifies it in the reply-to property of a request message (see Return Address). A well-behaved replier will send the reply back on the specified queue, one that the replier wouldn't even know about if it weren't a property of the request message. This is a simple approach the requestor can use to make sure that the replies always come back to it. 临时队列的缺点是,当连接关闭时,队列及其中的所有消息都会被删除。同样,临时队列无法提供保证交付;如果消息系统崩溃,那么连接就会丢失,因此队列及其消息也会丢失。 The downside with temporary queues is that when their Connection closes, the queue and any messages in it are deleted. Likewise, temporary queues cannot provide Guaranteed Delivery; if the messaging system crashes, then the connection is lost, so the queue and its messages are lost. JMS 还提供了QueueRequestor ,一个用于发送请求和接收回复的简单类。请求者包含用于发送请求的QueueSender和用于接收回复的 QueueReceiver 。每个请求者创建自己的临时队列来接收回复,并在请求的回复属性中指定该队列[ JMS 1.1 ]。请求者使得发送请求和接收回复变得非常简单: JMS also provides QueueRequestor, a simple class for sending requests and receiving replies. A requestor contains a QueueSender for sending requests and a QueueReceiver for receiving replies. Each requestor creates its own temporary queue for receiving replies and specifies that in the request's reply-to property [JMS 1.1]. A requestor makes sending a request and receiving a reply very simple: QueueConnection connection = // 获取连接 Queue requestQueue = // 获取队列 Message request = // 创建请求消息 QueueSession 会话 = connection.createQueueSession(false, QueueConnection connection = // obtain the connection Queue requestQueue = // obtain the queue Message request = // create the request message QueueSession session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); QueueRequestor requestor = new QueueRequestor(session, requestQueue ); Message reply = requestor.request(request); 一种方法请求发送请求消息并阻塞,直到收到回复消息。 One methodrequestsends the request message and blocks until it receives the reply message. QueueRequestor使用的 TemporaryQueue是一个点对点Channel 。它的发布-订阅通道等效项是TemporaryTopic 和TopicRequestor 。 TemporaryQueue, used by QueueRequestor, is a Point-to-Point Channel. Its Publish-Subscribe Channel equivalents are TemporaryTopic and TopicRequestor. |
我的应用程序正在使用消息传递来执行Request -Reply 。
My application is using Messaging to perform a Request-Reply.
|
回复者如何知道将回复发送到哪里? How does a replier know where to send the reply? |
消息通常被认为是完全独立的,因此任何发送者都可以随时在任何通道上发送消息。然而,消息通常是相关联的。对于请求-应答对,两条消息看起来是独立的,但是应答消息与引起它的请求消息具有一一对应的关系。因此,处理请求消息的应答者不能简单地在它想要的任何通道上发送应答消息;它必须将其发送到请求者期望回复的通道上。
Messages are often thought of as completely independent, such that any sender can send a message on any channel whenever it likes. However, messages are often associated. With Request-Reply pairs, two messages appear independent, but the reply message has a one-to-one correspondence with the request message that caused it. Thus, the replier that processes the request message cannot simply send the reply message on any channel it wants; it must send it on the channel on which the requestor expects the reply.
每个接收者都可以自动知道在哪个通道上发送回复,但是硬编码这样的假设会使软件不太灵活并且更难以维护。此外,单个回复者可以处理来自多个不同请求者的呼叫,因此每条消息的回复通道都不相同;这取决于哪个请求者发送了请求消息。
Each receiver could automatically know which channel to send replies on, but hard-coding such assumptions makes the software less flexible and more difficult to maintain. Furthermore, a single replier could be processing calls from several different requestors, so the reply channel is not the same for every message; it depends on which requestor sent the request message.
不确定将回复发送到哪里
Uncertain Where to Send Replies
请求者可能不希望将回复发送回自己。相反,它可以具有关联的回调处理器来处理回复,并且回调处理器可以监视与请求者不同的通道(或者请求者可以根本不监视任何通道)。请求者可以有多个回调处理器,要求将来自同一请求者的不同请求的回复发送到不同的处理器。
A requestor potentially may not want a reply sent back to itself. Rather, it may have an associated callback processor to process replies, and the callback processor may monitor a different channel than the requestor does (or the requestor may not monitor any channels at all). The requestor could have multiple callback processors, requiring replies for different requests from the same requestor to be sent to different processors.
回复通道不一定会将回复传输回请求者;它将把它们传输给请求者想要处理回复的任何人,因为它正在侦听请求者指定的通道。因此,知道哪个请求者发送了请求或者请求在哪个通道上发送并不一定告诉回复者在哪个通道上发送回复。即使这样做,回复者仍然必须推断对特定请求者或请求通道使用哪个回复通道。请求更容易明确指定要使用的回复通道。请求者需要一种方法来告诉回复者在哪里以及如何发回回复。
The reply channel will not necessarily transmit replies back to the requestor; it will transmit them to whomever the requestor wants to process the replies, because it's listening to the channel the requestor specified. So, knowing what requestor sent a request or what channel it was sent on does not necessarily tell the replier what channel to send the reply on. Even if it did, the replier would still have to infer which reply channel to use for a particular requestor or request channel. It's easier for the request to explicitly specify which reply channel to use. What is needed is a way for the requestor to tell the replier where and how to send back a reply.
|
请求消息应包含返回地址,指示将回复消息发送到何处。 The request message should contain a Return Address that indicates where to send the reply message. |
这样,回复者不需要知道将回复发送到哪里;它只需从请求消息中获取回复通道地址即可。如果发送给同一回复者的不同消息需要回复到不同的位置,则回复者可以根据每个请求消息确定将该请求的回复发送到哪里。这封装了请求者内用于请求和回复的通道的知识,因此这些决策不必在回复者内进行硬编码。返回地址被放置在消息的标头中,因为它不是正在传输的应用程序数据的一部分。
This way, the replier does not need to know where to send the reply; it can just obtain the reply channel address from the request message. If different messages to the same replier require replies to different places, the replier can determine from each request message where to send the reply for that request. This encapsulates the knowledge of what channels to use for requests and replies within the requestor so those decisions do not have to be hard-coded within the replier. A Return Address is put in the header of a message because it's not part of the application data being transmitted.
消息的返回地址类似于电子邮件中的回复字段。回复电子邮件地址通常与发件人地址相同,但发件人可以将其设置为不同的地址,以便在用于发送原始邮件的帐户以外的帐户中接收回复。
A message's Return Address is analogous to the reply-to field in an e-mail message. The reply-to e-mail address is usually the same as the from address, but the sender can set it to a different address to receive replies in an account other than the one used to send the original message.
当回复通过返回地址指示的通道发回时,可能还需要一个相关标识符。返回地址告诉接收者将回复消息放在哪个通道上;相关标识符告诉发送者回复是针对哪个请求的。
When the reply is sent back over the channel indicated by the Return Address, it may also need a Correlation Identifier. The Return Address tells the receiver what channel to put the reply message on; the Correlation Identifier tells the sender which request a reply is for.
|
示例: JMS 回复属性 Example: JMS Reply-To Property JMS 消息具有返回地址的预定义属性JMSReplyTo 。它的类型是目的地(主题或队列),而不仅仅是目的地名称的字符串,这确保了目的地(例如,消息通道)确实存在,至少在发送请求时[JMS 1.1 ],[ Monson-哈菲尔]。 JMS messages have a predefined property for Return Addresses, JMSReplyTo. Its type is a Destination (a Topic or Queue) rather than just a string for the destination name, which ensures that the destination (e.g., Message Channel) really exists, at least when the request is sent [JMS 1.1], [Monson-Haefel]. 希望指定作为队列的回复通道的发送者可以这样做: A sender that wishes to specify a reply channel that is a Queue would do so like this: Queue requestQueue = // 指定请求目的地 QueuereplyQueue = //指定回复目的地 Message requestMessage = // 创建请求消息 requestMessage.setJMSReplyTo(replyQueue); MessageProducer requestSender = session.createProducer(requestQueue); requestSender.send(requestMessage); Queue requestQueue = // Specify the request destination Queue replyQueue = // Specify the reply destination Message requestMessage = // Create the request message requestMessage.setJMSReplyTo(replyQueue); MessageProducer requestSender = session.createProducer(requestQueue); requestSender.send(requestMessage); 然后,接收方将发送如下回复消息: Then, the receiver would send the reply message like this: Queue requestQueue = // 指定请求目的地 MessageConsumer requestReceiver = session.createConsumer Queue requestQueue = // Specify the request destination MessageConsumer requestReceiver = session.createConsumer (requestQueue); Message requestMessage = requestReceiver.receive(); Message replyMessage = // Create the reply message Destination replyQueue = requestMessage.getJMSReplyTo(); MessageProducer replySender = session.createProducer(replyQueue); replySender.send(replyMessage); |
|
示例: .NET 响应队列属性 Example: .NET Response-Queue Property .NET 消息还具有返回地址的预定义属性ResponseQueue 。它的类型是MessageQueue(例如, Message Channel),应用程序应将响应消息发送到[SysMsg]、[ Dickman ]的队列。 .NET messages also have a predefined property for Return Addresses, ResponseQueue. Its type is a MessageQueue (e.g., Message Channel), the queue that the application should send a response message to [SysMsg], [Dickman]. |
|
示例: Web 服务请求/响应 Example: Web Services Request/Response SOAP 1.2 合并了请求-响应消息交换模式[ SOAP 1.2 第 2 部分 ] ,但发送回复的地址未指定,因此是隐含的。此 SOAP 模式需要支持可选的返回地址,以真正使 SOAP 消息异步并使响应者与请求者脱钩。 SOAP 1.2 incorporates the Request-Response Message Exchange pattern [SOAP 1.2 Part 2], but the address to which to send the reply is unspecified and therefore implied. This SOAP pattern will need to support an optional Return Address to truly make SOAP messages asynchronous and to delink the responder from the requestor. 新兴的 WS-Addressing 标准通过指定如何识别 Web 服务端点以及要使用哪些 XML 元素来帮助解决这个问题。这样的地址可以在 SOAP 消息中使用来指定返回地址。请参阅第 14 章“结束语”中对 WS-Addressing 的讨论。 The emerging WS-Addressing standard helps address this issue by specifying how to identify a Web service endpoint and what XML elements to use. Such an address can be used in a SOAP message to specify a Return Address. See the discussion of WS-Addressing in Chapter 14, "Concluding Remarks." |
我的应用程序正在使用消息传递来执行请求-答复并已收到答复消息。
My application is using Messaging to perform a Request-Reply and has received a reply message.
|
收到回复的请求者如何知道这是针对哪个请求的回复? How does a requestor that has received a reply know which request this is the reply for? |
当一个进程通过远程过程调用调用另一个进程时,该调用是同步的,因此不会混淆哪个调用产生了给定结果。但消息传递是异步的,因此从调用者的角度来看,它会进行调用,然后稍后会出现结果。调用者可能甚至不记得发出过请求,或者可能发出了太多请求,以至于它不再知道这是哪一个请求的结果。现在,当调用者最终得到结果时,它可能不知道如何处理它,这违背了调用的初衷。
When one process invokes another via Remote Procedure Invocation, the call is synchronous, so there is no confusion about which call produced a given result. But Messaging is asynchronous, so from the caller's point of view, it makes the call, and then sometime later a result appears. The caller may not even remember making the request, or it may have made so many requests that it no longer knows which one this is the result for. Now, when the caller finally gets the result, it may not know what to do with it, which defeats the purpose of making the call in the first place.
无法匹配请求的回复
Cannot Match Reply to Request
调用者可以使用多种方法来避免这种混乱。它一次只能发出一个调用,并在发送另一个请求之前等待回复,因此在任何给定时间最多有一个未完成的请求。然而,这将大大降低处理吞吐量。调用者可以假设它将按照发送请求的顺序接收回复,但消息传递并不能保证消息以什么顺序传递(请参阅重新排序器) ,并且所有请求可能不会花费相同的时间来处理,因此调用者的假设是错误的。调用者可以设计其请求,以便他们不需要回复,但这种限制将使消息传递对于许多目的来说毫无用处。
There are a couple of approaches the caller can use to avoid this confusion. It can make just one call at a time and wait for a reply before sending another request, so there is at most one outstanding request at any given time. This, however, will greatly slow processing throughput. The caller could assume that it will receive replies in the same order it sent requests, but messaging does not guarantee what order messages are delivered in (see Resequencer), and all requests may not take the same amount of time to process, so the caller's assumption would be faulty. The caller could design its requests so that they do not need replies, but this constraint would make messaging useless for many purposes.
调用者需要的是回复消息具有指向请求消息的指针或引用,但消息并不存在于可以由变量引用的稳定内存空间中。但是,消息可能具有某种键,即唯一标识符,例如关系数据库表中行的键。这样的唯一标识符可用于从其他消息、使用该消息的客户端等中识别该消息。
What the caller needs is for the reply message to have a pointer or reference to the request message, but messages do not exist in a stable memory space where they can be referenced by variables. However, a message could have some sort of key, a unique identifier like the key for a row in a relational database table. Such a unique identifier could be used to identify the message from other messages, clients that use the message, and so on.
|
每个回复消息都应包含一个Correlation Identifier ,这是一个唯一标识符,指示此回复针对哪个请求消息。 Each reply message should contain a Correlation Identifier, a unique identifier that indicates which request message this reply is for. |
相关标识符有六个部分。
There are six parts to Correlation Identifier.
请求者 通过发送请求并等待回复来执行业务任务的应用程序。
Requestor An application that performs a business task by sending a request and waiting for a reply.
回复者 接收请求、完成请求然后发送回复的另一个应用程序。它从请求中获取请求 ID,并将其作为相关 ID 存储在回复中。
Replier Another application that receives the request, fulfills it, and then sends the reply. It gets the request ID from the request and stores it as the correlation ID in the reply.
请求从请求者发送到回复者的 消息,包含请求 ID。
Request A Message sent from the requestor to the replier, containing a request ID.
回复从回复者发送到请求者的消息 ,包含相关 ID。
Reply A Message sent from the replier to the requestor, containing a correlation ID.
请求 ID 请求中唯一标识该请求的令牌。
Request ID A token in the request that uniquely identifies the request.
相关 ID 回复中的令牌,其值与请求中的请求 ID 相同。
Correlation ID A token in the reply that has the same value as the request ID in the request.
这就是相关标识符的工作原理:当请求者创建请求消息时,它会为该请求分配一个请求 IDan 标识符,该标识符与所有其他当前未完成的请求(即尚未收到回复的请求)的标识符不同。当回复者处理请求时,它会保存请求 ID 并将该 ID 添加到回复中作为相关 ID。当请求者处理回复时,它使用相关ID来知道回复是针对哪个请求的。这被称为关联标识符,因为调用者使用标识符来关联(即,匹配;显示关系)每个对引起它的请求的回复。
This is how a Correlation Identifier works: When the requestor creates a request message, it assigns the request a request IDan identifier that is different from those for all other currently outstanding requests, that is, requests that do not yet have replies. When the replier processes the request, it saves the request ID and adds that ID to the reply as a correlation ID. When the requestor processes the reply, it uses the correlation ID to know which request the reply is for. This is called a Correlation Identifier because of the way the caller uses the identifier to correlate (i.e., match; show the relationship) each reply to the request that caused it.
正如消息传递的常见情况一样,请求者和回复者必须就几个细节达成一致。他们必须就请求 ID 属性的名称和类型达成一致,并且必须就相关 ID 属性的名称和类型达成一致。同样,请求和回复消息格式必须定义这些属性或允许将它们添加为自定义属性。例如,如果请求者将请求 ID 存储在名为request_id的第一级 XML 元素中,并且该值是一个整数,则回复者必须知道这一点,以便能够找到请求 ID 值并正确处理它。请求ID值和关联ID值通常是同一类型;如果不是,请求者必须知道回复者如何将请求 ID 转换为回复 ID。
As is often the case with messaging, the requestor and replier must agree on several details. They must agree on the name and type of the request ID property, and they must agree on the name and type of the correlation ID property. Likewise, the request and reply message formats must define those properties or allow them to be added as custom properties. For example, if the requestor stores the request ID in a first-level XML element named request_id and the value is an integer, the replier has to know this so that it can find the request ID value and process it properly. The request ID value and correlation ID value are usually of the same type; if not, the requestor has to know how the replier will convert the request ID to the reply ID.
此模式是异步完成令牌模式 [ POSA2 ] 的更简单的、特定于消息传递的版本。请求者是发起者,回复者是服务,请求者中处理回复的消费者是完成处理程序,消费者用来匹配请求回复的相关标识符是异步完成令牌。
This pattern is a simpler, messaging-specific version of the Asynchronous Completion Token pattern [POSA2]. The requestor is the Initiator, the replier is the Service, the consumer in the requestor that processes the reply is the Completion Handler, and the Correlation Identifier the consumer uses to match the reply to the request is the Asynchronous Completion Token.
相关 ID(以及请求 ID)通常放置在消息的标头中而不是正文中。ID 不是请求者尝试与应答者通信的命令或数据的一部分。事实上,回复者根本没有真正使用该 ID;而是使用了该 ID。它只是保存请求中的 ID 并将其添加到回复中以利于请求者。由于消息正文是在两个系统之间传输的内容,而 ID 不是其中的一部分,因此 ID 位于标头中。
A correlation ID (and also the request ID) is usually put in the header of a message rather than in the body. The ID is not part of the command or data the requestor is trying to communicate to the replier. In fact, the replier does not really use the ID at all; it just saves the ID from the request and adds it to the reply for the requestor's benefit. Since the message body is the content being transmitted between the two systems, and the ID is not part of that, the ID goes in the header.
该模式的要点是,回复消息包含一个标记(相关 ID),用于标识相应的请求(通过其请求 ID)。有几种不同的方法可以实现这一目标。
The gist of the pattern is that the reply message contains a token (the correlation ID) that identifies the corresponding request (via its request ID). There are several different approaches for achieving this.
最简单的方法是每个请求包含一个唯一的 ID,例如消息 ID,并且响应的相关 ID 是该请求的唯一 ID。这将回复与其相应的请求相关联。然而,当请求者尝试处理回复时,了解请求消息通常没有多大帮助。请求者真正想要的是提醒是什么业务任务导致其首先发送请求,以便请求者可以使用回复中的数据完成业务任务。
The simplest approach is for each request to contain a unique ID, such as a message ID, and for the response's correlation ID to be the request's unique ID. This relates the reply to its corresponding request. However, when the requestor is trying to process the reply, knowing the request message often isn't very helpful. What the requestor really wants is a reminder of what business task caused it to send the request in the first place so that the requestor can complete the business task using the data in the reply.
业务任务,例如需要执行股票交易或发送采购订单,可能有自己唯一的业务对象标识符(例如订单ID),因此可以使用业务任务的唯一ID作为请求-回复相关 ID。然后,当请求者获得回复及其相关 ID 时,它可以绕过请求消息,直接前往其任务首先引发该请求的业务对象。在这种情况下,请求者和回复者不应使用消息的内置请求消息 ID 和回复相关 ID 属性,而应在请求和回复中使用自定义业务对象 ID 属性,该属性标识此请求的任务的业务对象-回复消息对正在执行。
The business task, such as needing to execute a stock trade or to ship a purchase order, probably has its own unique business object identifier (such as an order ID), so that the business task's unique ID can be used as the request-reply correlation ID. Then, when the requestor gets the reply and its correlation ID, it can bypass the request message and go straight to the business object whose task caused the request in the first place. In this case, rather than use the messages' built-in request message ID and reply correlation ID properties, the requestor and replier should use a custom business object ID property in both the request and the reply that identifies the business object whose task this request-reply message pair is performing.
一种折衷的方法是让请求者保留请求 ID 和业务对象 ID 的映射。当请求者希望将对象 ID 保密时,或者当请求者无法控制回复者的实现并且只能依赖回复者将请求的消息 ID 复制到回复的相关 ID 中时,这尤其有用。在这种情况下,当请求者收到回复时,它会在映射中查找相关 ID 以获取业务对象 ID,然后使用该 ID 来恢复使用回复数据执行业务任务。
A compromise approach is for the requestor to keep a map of request IDs and business object IDs. This is especially useful when the requestor wants to keep the object IDs private or when the requestor has no control over the replier's implementation and can only depend on the replier copying the request's message ID into the reply's correlation ID. In this case, when the requestor gets the reply, it looks up the correlation ID in the map to get the business object ID and then uses that to resume performing the business task using the reply data.
消息具有单独的消息 ID 和相关 ID 属性,以便可以链接请求-回复消息对。当一个请求引起回复,而该回复又是另一个请求引起另一个回复,依此类推时,就会发生这种情况。一条消息的消息ID唯一标识它所代表的请求;如果该消息还具有相关 ID,则该消息也是对另一个请求消息的回复,如相关 ID 所标识的。
Messages have separate message ID and correlation ID properties so that request-reply message pairs can be chained. This occurs when a request causes a reply, and the reply is in turn another request that causes another reply, and so on. A message's message ID uniquely identifies the request it represents; if the message also has a correlation ID, then the message is also a reply for another request message, as identified by the correlation ID.
请求-回复链
Request-Reply Chaining
仅当应用程序想要追溯从最新回复到原始请求的消息路径时,链接才有用。通常,应用程序想要知道的只是原始请求,无论中间发生了多少个回复步骤。在这种情况下,一旦消息具有非空相关 ID,它就是一个回复,并且由此产生的所有后续回复也应该使用相同的相关 ID。
Chaining is only useful if an application wants to retrace the path of messages from the latest reply back to the original request. Often, all the application wants to know is the original request, regardless of how many reply steps occurred in between. In this situation, once a message has a non-null correlation ID, it is a reply, and all subsequent replies that result from it should also use the same correlation ID.
虽然相关标识符用于将回复与其请求进行匹配,但该请求还可能具有一个返回地址,用于说明将回复放在哪个通道上。相关标识符用于将回复消息与其请求进行匹配,而消息序列的标识符用于指定消息在来自同一发送者的一系列消息中的位置。
While a Correlation Identifier is used to match a reply with its request, the request may also have a Return Address that states what channel to put the reply on. Whereas a correlation identifier is used to matching a reply message with its request, a Message Sequence's identifiers are used to specify a message's position within a series of messages from the same sender.
|
示例: JMS 相关 ID 属性 Example: JMS Correlation-ID Property JMS 消息有一个用于关联标识符的预定义属性:JMSCorrelationID ,它通常与另一个预定义属性 JMSMessageID [ JMS 1.1 ] 、 [ Monson -Haefel ]结合使用。 回复消息的相关 ID 根据请求的消息 ID 设置,如下所示: JMS messages have a predefined property for correlation identifiers: JMSCorrelationID, which is typically used in conjunction with another predefined property, JMSMessageID [JMS 1.1], [Monson-Haefel]. A reply message's correlation ID is set from the request's message ID like this: Message requestMessage = // 获取请求消息 MessagereplyMessage = // 创建回复消息 String requestID = requestMessage.getJMSMessageID(); replyMessage.setJMSCorrelationID(requestID); Message requestMessage = // Get the request message Message replyMessage = // Create the reply message String requestID = requestMessage.getJMSMessageID(); replyMessage.setJMSCorrelationID(requestID); |
|
示例: .NET CorrelationId 属性 Example: .NET CorrelationId Property .NET 中的每个消息都有一个CorrelationId属性,它是确认消息中的一个字符串,通常设置为原始消息的 ID。MessageQueue还具有特殊的查看和接收方法PeekByCorrelationId(string)和ReceiveByCorrelationId(string) ,用于查看和使用指定相关 ID 的队列(如果有)上的消息(请参阅选择性消费者)[ SysMsg ]、[ Dickman ] 。 Each Message in .NET has a CorrelationId property, a string in an acknowledgment message that is usually set to the ID of the original message. MessageQueue also has special peek and receive methods, PeekByCorrelationId(string) and ReceiveByCorrelationId(string), for peeking at and consuming the message on the queue (if any) with the specified correlation ID (see Selective Consumer) [SysMsg], [Dickman]. |
|
示例: Web 服务请求-响应 Example: Web Services Request-Response 自 SOAP 1.1 [ SOAP 1.1 ] 起,Web 服务标准并未为异步消息传递提供很好的支持,但 SOAP 1.2 开始对此进行规划。SOAP 1.2 合并了请求-响应消息交换模式[ SOAP 1.2 第 2 部分 ] ,这是异步 SOAP 消息传递的基本部分。然而,请求-响应模式并不强制要求支持“多个正在进行的请求”,因此它没有定义标准的相关标识符字段,甚至没有定义可选字段。 Web services standards, as of SOAP 1.1 [SOAP 1.1], do not provide very good support for asynchronous messaging, but SOAP 1.2 starts to plan for it. SOAP 1.2 incorporates the Request-Response Message Exchange pattern [SOAP 1.2 Part 2], a basic part of asynchronous SOAP messaging. However, the request-response pattern does not mandate support for "multiple ongoing requests," so it does not define a standard Correlation Identifier field, not even an optional one. 实际上,服务请求者通常确实需要多个未完成的请求。“Web 服务体系结构使用场景”[ WSAUS ] 讨论了几种不同的异步 Web 服务场景。其中四个请求-响应、远程过程调用(其中传输协议不直接支持[同步]请求-响应)、多个异步响应和异步消息传送使用SOAP标头中的message-id和response-to 字段将响应与它的要求。这是请求-响应示例: As a practical matter, service requestors often do require multiple outstanding requests. "Web Services Architecture Usage Scenarios" [WSAUS] discusses several different asynchronous Web services scenarios. Four of themRequest-Response, Remote Procedure Call (where the transport protocol does not support [synchronous] request-response directly), Multiple Asynchronous Responses, and Asynchronous Messaginguse message-id and response-to fields in the SOAP header to correlate a response to its request. This is the request-response example: 包含消息标识符的 SOAP 请求消息<?xml 版本=“1.0”?>
<env:信封 xmlns:env="http://www.w3.org/2002/06/soap-envelope">
<环境:标头>
<n:MsgHeader xmlns:n="http://example.org/requestresponse">
<n:MessageId>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
<?xml version="1.0" ?>
<env:Envelope xmlns:env="http://www.w3.org/2002/06/soap-envelope">
<env:Header>
<n:MsgHeader xmlns:n="http://example.org/requestresponse">
<n:MessageId>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
:MessageId>
</n:MsgHeader>
</env:Header>
<env:Body>
........
</env:Body>
</env:Envelope>
包含与原始请求相关的 SOAP 响应消息<?xml 版本=“1.0”?>
<env:信封 xmlns:env="http://www.w3.org/2002/06/soap-envelope">
<环境:标头>
<n:MsgHeader xmlns:n="http://example.org/requestresponse">
<n:MessageId>uuid:09233523-567b-2891-b623-9dke28yod7m9</n
<?xml version="1.0" ?>
<env:Envelope xmlns:env="http://www.w3.org/2002/06/soap-envelope">
<env:Header>
<n:MsgHeader xmlns:n="http://example.org/requestresponse">
<n:MessageId>uuid:09233523-567b-2891-b623-9dke28yod7m9</n
:MessageId>
<n:ResponseTo>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
:ResponseTo>
</n:MsgHeader>
</env:Header>
<env:Body>
........
</env:Body>
</env:Envelope>
与JMS和.NET示例一样,在该SOAP示例中,请求消息包含唯一的消息标识符,并且响应消息包含响应(例如,相关ID)字段,其值是请求消息的消息标识符。 Like the JMS and .NET examples, in this SOAP example, the request message contains a unique message identifier, and the response message contains a response (e.g., a correlation ID) field whose value is the message identifier of the request message. |
我的应用程序需要将大量数据发送到另一个进程,这超出了一条消息所能容纳的范围。或者,我的应用程序发出了一个请求,其回复对于单个消息包含太多数据。
My application needs to send a huge amount of data to another process, more than may fit in a single message. Or, my application has made a request whose reply contains too much data for a single message.
|
消息传递如何传输任意大量的数据? How can messaging transmit an arbitrarily large amount of data? |
消息可以任意大,这很好,但单个消息可以容纳的数据量存在实际限制。一些消息传递实现对消息的大小设置了绝对限制。其他实现允许消息变得相当大,但大消息仍然会损害性能。即使消息传递实现允许大消息,消息生产者或消费者也可能会对其一次可以处理的数据量设置限制。例如,许多基于 COBOL 和基于大型机的系统将仅消耗或生成 32 Kb 块的数据。
It's nice to think that messages can be arbitrarily large, but there are practical limits to how much data a single message can hold. Some messaging implementations place an absolute limit on how big a message can be. Other implementations allow messages to get quite big, but large messages nevertheless hurt performance. Even if the messaging implementation allows large messages, the message producer or consumer may place a limit on the amount of data it can process at once. For example, many COBOL-based and mainframe-based systems will consume or produce data only in 32 Kb chunks.
那么,你如何解决这个问题呢?一种方法是限制您的应用程序,使其永远不需要传输比消息传递层可以在单个消息中存储的数据更多的数据。不过,这是一个任意限制,它可能会阻止您的应用程序产生所需的功能。如果大量数据是请求的结果,则调用者可以发出多个请求,每个请求对应一个结果块,但这会增加网络流量,并且假设调用者甚至知道需要多少个结果块。接收器可以监听数据块,直到没有更多的数据块(但它如何知道没有更多的数据块?),然后尝试找出如何将这些块重新组装成原始的大数据块,但这将是容易出错。
So, how do you get around this? One approach is to limit your application so it never needs to transfer more data than the messaging layer can store in a single message. This is an arbitrary limit, though, which can prevent your application from producing the desired functionality. If the large amount of data is the result of a request, the caller could issue multiple requests, one for each result chunk, but that increases network traffic and assumes the caller even knows how many result chunks will be needed. The receiver could listen for data chunks until there are no more (but how does it know there aren't any more?) and then try to figure out how to reassemble the chunks into the original, large piece of data, but that would be error-prone.
灵感来自于邮购公司有时用多个盒子运送订单的方式。如果有三个箱子,发货人会将它们标记为“1 of 3”、“2 of 3”和“3 of 3”,以便收件人知道他收到了哪些箱子以及是否已收到全部箱子。诀窍是将相同的技术应用于消息传递。
Inspiration comes from the way a mail order company sometimes ships an order in multiple boxes. If there are three boxes, the shipper marks them as "1 of 3," "2 of 3," and "3 of 3," so the receiver knows which ones he has received and whether he has received all of them. The trick is to apply the same technique to messaging.
|
每当需要将大量数据分解为消息大小的块时,请将数据作为消息序列发送,并使用序列标识字段标记每个消息。 Whenever a large set of data needs to be broken into message-size chunks, send the data as a Message Sequence and mark each message with sequence identification fields. |
三个消息序列标识字段如下。
The three Message Sequence identification fields are as follows.
序列标识符 将此消息簇与其他消息簇区 分开来。
Sequence identifier Distinguishes this cluster of messages from others.
位置标识符 唯一地标识序列中的每条消息并按顺序对其进行排序。
Position identifier Uniquely identifies and sequentially orders each message in a sequence.
大小 或 结束指示符 指定群集中的消息数或标记群集中的最后一条消息(其位置标识符随后指定群集的大小)。
Size or End indicator Specifies the number of messages in the cluster or marks the last message in the cluster (whose position identifier then specifies the size of the cluster).
序列通常被设计为使得序列中的每个消息指示序列的总大小,即该序列中的消息的数量。作为替代方案,您可以设计序列,以便每条消息都指示它是否是该序列中的最终消息。
The sequences are typically designed so that each message in a sequence indicates the total size of the sequencethat is, the number of messages in that sequence. As an alternative, you can design the sequences so that each message indicates whether it is the final message in that sequence.
带结束指示器的消息序列
Message Sequence with End Indicator
假设一组数据需要作为三个消息的集群发送。三消息簇的序列标识符将是某个唯一的ID。每条消息的位置标识符都不同:1、2 或 3(假设编号从 1 开始,而不是从 0 开始)。如果发送方从一开始就知道消息总数,则每个消息的序列大小为 3。如果发送方直到用完要发送的数据才知道消息总数(例如,发送方正在流式传输数据) ),除了最后一条消息之外的每条消息都会有一个错误的“序列结束”标志。当发送方准备好发送序列中的最后一条消息时,它将将该消息的序列结束标志设置为 true。无论哪种方式,
Let's say a set of data needs to be sent as a cluster of three messages. The sequence identifier of the three-message cluster will be some unique ID. The position identifier for each message will be different: either 1, 2, or 3 (assuming that numbering starts from 1, not 0). If the sender knows the total number of messages from the start, the sequence size for each message is 3. If the sender does not know the total number of messages until it runs out of data to send (e.g., the sender is streaming the data), each message except the last will have a "sequence end" flag that is false. When the sender is ready to send the final message in the sequence, it will set that message's sequence end flag as true. Either way, the position identifiers and sequence size/end indicator will give the receiver enough information to reassemble the parts back into the whole, even if the parts are not received in sequential order.
如果接收者期望一个Message Sequence ,那么发送给它的每条消息都应该作为序列的一部分发送,即使它只是一个序列。否则,当发送不带序列标识字段的单部分消息时,接收方可能会对丢失的字段感到困惑,并可能得出消息无效的结论(请参阅无效消息通道)。
If the receiver expects a Message Sequence, then every message sent to it should be sent as part of a sequence, even if it is only a sequence of one. Otherwise, when a single-part message is sent without the sequence identification fields, the receiver may become confused by the missing fields and may conclude that the message is invalid (see Invalid Message Channel).
如果接收者按顺序获取了部分消息,但未获取全部消息,则应将其收到的消息重新路由到Invalid Message Channel 。
If a receiver gets some of the messages in a sequence but doesn't get all of them, it should reroute the ones it did receive to the Invalid Message Channel.
应用程序可能希望使用事务客户端用于发送和接收序列。发送者可以使用单个事务按顺序发送所有消息。这样,在所有消息发送完毕之前,不会发送任何消息。同样,接收者可能希望使用单个事务来接收消息,以便在接收到所有消息之前不会真正消耗任何消息。如果序列中的任何消息丢失,接收方可以选择回滚事务,以便稍后可以使用这些消息。在许多消息传递系统实现中,如果在一个事务中发送一系列消息,则将按照发送的顺序接收消息,这简化了接收者将数据重新组合在一起的工作。
An application may wish to use a Transactional Client for sending and receiving sequences. The sender can send all of the messages in a sequence using a single transaction. This way, none of the messages will be delivered until all of them have been sent. Likewise, a receiver may wish to use a single transaction to receive the messages so that it does not truly consume any of the messages until it receives all of them. If any of the messages in the sequence are missing, the receiver can choose to roll back the transaction so that the messages can be consumed later. In many messaging system implementations, if a sequence of messages is sent in one transaction, the messages will be received in the order they are sent, which simplifies the receiver's job of putting the data back together.
当消息序列是请求-回复中的回复消息时,序列标识符和相关标识符通常是相同的东西。如果发送请求的应用程序期望对同一请求有多个响应,并且一个或多个响应可能分为多个部分,则它们将是分开的。当只期望一个响应时,则唯一地标识该响应及其序列是允许的,但是多余的。
When the Message Sequence is the reply message in a Request-Reply, the sequence identifier and the Correlation Identifier are usually the same thing. They would be separate if the application sending the request expected multiple responses to the same request, and one or more of the responses could be in multiple parts. When only one response is expected, then uniquely identifying the response and its sequence is permissible but redundant.
消息序列往往与竞争消费者或消息调度程序不兼容。如果不同的消费者/执行者按顺序接收到不同的消息,则没有一个接收者能够在不相互交换消息内容的情况下重新组装原始数据。因此,消息序列应该通过消息通道与单个消费者一起传输。
Message Sequence tends not to be compatible with Competing Consumers or Message Dispatcher. If different consumers/performers receive different messages in a sequence, none of the receivers will be able to reassemble the original data without exchanging message contents with each other. Thus, a message sequence should be transmitted via a Message Channel with a single consumer.
消息序列的替代方法是使用声明检查。如果应用程序都可以访问公共数据库或文件系统,则无需在两个应用程序之间传输大型文档,而是存储文档并仅在单个消息中传输文档的密钥。
An alternative to Message Sequence is to use a Claim Check. Rather than transmitting a large document between two applications, if the applications both have access to a common database or file system, store the document and just transmit a key to the document in a single message.
使用消息序列类似于使用拆分器将大消息分解为消息序列,并使用聚合器将消息序列重新组合回单个消息。拆分器和聚合器使原始消息和最终消息变得非常大,而消息序列使消息端点能够在发送任何消息之前拆分数据,并在接收消息后聚合数据。
Using Message Sequence is similar to using a Splitter to break up a large message into a sequence of messages and using an Aggregator to reassemble the message sequence back into a single message. Splitter and Aggregator enable the original and final messages to be very large, whereas Message Sequence enables the Message Endpoints to split the data before any messages are sent and to aggregate the data after the messages are received.
|
示例: 大文档传输 Example: Large Document Transfer 想象一下,发送者需要向接收者发送一个非常大的文档,该文档太大以至于无法容纳在单个消息中,或者一次发送所有文档是不切实际的。在这种情况下,应该将文档分成多个部分,并且每个部分都可以作为消息发送。每条消息都需要指示它在序列中的位置以及总共有多少条消息。例如,MSMQ 消息的最大大小为 4 MB。[ Dickman ]讨论了如何在 MSMQ 中发送多部分消息序列。 Imagine that a sender needs to send a receiver an extremely large document, so large that it will not fit within a single message or is impractical to send all at once. In this case, the document should be broken into parts, and each part can be sent as a message. Each message needs to indicate its position in the sequence and how many messages there are in all. For example, the maximum size of an MSMQ message is 4 MB. [Dickman] discusses how to send a multipart message sequence in MSMQ. |
|
示例: 多项目查询 Example: Multi-Item Query 考虑一个请求特定作者的所有书籍列表的查询。由于这可能是一个非常大的列表,因此消息传递设计可能会选择将每个匹配项作为单独的消息返回。然后,每条消息都需要指示该回复所针对的查询、消息在序列中的位置以及预期的消息数量。 Consider a query that requests a list of all books by a certain author. Because this could be a very large list, the messaging design might choose to return each match as a separate message. Then, each message needs to indicate the query this reply is for, the message's position in the sequence, and how many messages to expect. |
|
示例: 分布式查询 Example: Distributed Query 考虑由多个接收者部分执行的查询。如果部件有某种顺序,则需要在回复消息中指出,以便可以正确组装完整的回复。每个接收者都需要知道其在整个顺序中的位置,并且需要指示该位置是回复的消息序列。 Consider a query that is performed in parts by multiple receivers. If the parts have some order to them, this will need to be indicated in the reply messages so that the complete reply can be assembled properly. Each receiver will need to know its position in the overall order and will need to indicate that position is the reply's message sequence. |
|
示例: JMS 和 .NET Example: JMS and .NET JMS 和 .NET 都没有用于支持消息序列的内置属性。因此,消息传递应用程序必须实现自己的序列字段。在 JMS 中,应用程序可以在标头中定义自己的属性,因此这是一个选项。.NET 不在标头中提供应用程序定义的属性。这些字段也可以在消息正文中定义。请记住,如果序列的接收者需要根据消息的序列来过滤消息,那么如果字段存储在标头中而不是正文中,则这种过滤会更简单。 Neither JMS nor .NET has built-in properties for supporting message sequences. Therefore, messaging applications must implement their own sequence fields. In JMS, an application can define its own properties in the header, so that is an option. .NET does not provide application-defined properties in the header. The fields could also be defined in the message body. Keep in mind that if a receiver of the sequence needs to filter for messages based on their sequence, such filtering is much simpler to do if the field is stored in the header rather than in the body. |
|
示例: Web 服务:多个异步响应 Example: Web Services: Multiple Asynchronous Responses 目前,Web 服务标准并未为异步消息传递提供很好的支持,但 W3C 已开始考虑如何提供支持。“Web 服务体系结构使用场景”[ WSAUS ] 讨论了几种不同的异步 Web 服务场景。其中之一是多重异步响应,它使用 SOAP 标头中的message-id和response-to字段来关联对请求的响应,并使用正文中的序列号和total-in-sequence字段来按顺序标识响应。这是多重响应示例: Web services standards currently do not provide very good support for asynchronous messaging, but the W3C has started to think about how it could. "Web Services Architecture Usage Scenarios" [WSAUS] discusses several different asynchronous Web services scenarios. One of themMultiple Asynchronous Responsesuses message-id and response-to fields in the SOAP header to correlate a response to the request, and sequence-number and total-in-sequence fields in the body to sequentially identify the responses. This is the multiple responses example: 包含消息标识符的 SOAP 请求消息<?xml 版本=“1.0”?>
<env:信封 xmlns:env="http://www.w3.org/2002/06/soap-envelope">
<环境:标头>
<n:MsgHeader xmlns:n="http://example.org/requestresponse">
<n:MessageId>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
<?xml version="1.0" ?>
<env:Envelope xmlns:env="http://www.w3.org/2002/06/soap-envelope">
<env:Header>
<n:MsgHeader xmlns:n="http://example.org/requestresponse">
<n:MessageId>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
:MessageId>
</n:MsgHeader>
</env:Header>
<env:Body>
........
</env:Body>
</env:Envelope>
第一个 SOAP 响应消息包含与原始请求的排序和关联<?xml 版本=“1.0”?>
<env:信封 xmlns:env="http://www.w3.org/2002/06/soap-envelope">
<环境:标头>
<n:MsgHeader xmlns:n="http://example.org/requestresponse">
<!-- 每个响应消息的 MessageId 都是唯一的 -->
<!-- 每个响应消息的 ResponseTo 都是不变的
<?xml version="1.0" ?>
<env:Envelope xmlns:env="http://www.w3.org/2002/06/soap-envelope">
<env:Header>
<n:MsgHeader xmlns:n="http://example.org/requestresponse">
<!-- MessageId will be unique for each response message -->
<!-- ResponseTo will be constant for each response message
in the sequence-->
<n:MessageId>uuid:09233523-567b-2891-b623-9dke28yod7m9</n
:MessageId>
<n:ResponseTo>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
:ResponseTo>
</n:MsgHeader>
<s:Sequence xmlns:s="http://example.org/sequence">
<s:SequenceNumber>1</s:SequenceNumber>
<s:TotalInSequence>5</s:TotalInSequence>
</s:Sequence>
</env:Header>
<env:Body>
........
</env:Body>
</env:Envelope>
最终 SOAP 响应消息包含排序以及与原始请求的关联<?xml 版本=“1.0”?>
<env:信封 xmlns:env="http://www.w3.org/2002/06/soap-envelope">
<环境:标头>
<n:MsgHeader xmlns:n="http://example.org/requestresponse">
<!-- 每个响应消息的 MessageId 都是唯一的 -->
<!-- 每个响应消息的 ResponseTo 都是不变的
<?xml version="1.0" ?>
<env:Envelope xmlns:env="http://www.w3.org/2002/06/soap-envelope">
<env:Header>
<n:MsgHeader xmlns:n="http://example.org/requestresponse">
<!-- MessageId will be unique for each response message -->
<!-- ResponseTo will be constant for each response message
in the sequence-->
<n:MessageId>uuid:40195729-sj20-pso3-1092-p20dj28rk104</n
:MessageId>
<n:ResponseTo>uuid:09233523-345b-4351-b623-5dsf35sgs5d6</n
:ResponseTo>
</n:MsgHeader>
<s:Sequence xmlns:s="http://example.org/sequence">
<s:SequenceNumber>5</s:SequenceNumber>
<s:TotalInSequence>5</s:TotalInSequence>
</s:Sequence>
</env:Header>
<env:Body>
........
</env:Body>
</env:Envelope>
标头中的消息ID用作响应中的序列标识符。每个响应中的sequence-number和total-in-sequence分别是位置标识符和大小指示符。 The message-id in the header is used as the sequence identifier in the responses. The sequence-number and total-in-sequence in each response are a position identifier and a size indicator respectively. |
我的应用程序正在使用消息传递。如果在一定时间内未收到消息数据或请求,则该消息是无用的,应被忽略。
My application is using Messaging. If a Messages data or request is not received by a certain time, it is useless and should be ignored.
|
发件人如何指示消息何时应被视为过时,从而不应被处理? How can a sender indicate when a message should be considered stale and thus shouldn't be processed? |
消息传递实际上保证了消息最终将传递给接收者。它无法保证交货可能需要多长时间。例如,如果连接发送者和接收者的网络停机一周,则可能需要一周的时间才能传递消息。即使参与者(发送者、网络和接收者)不可靠,消息传递也是高度可靠的,但在不可靠的情况下消息传输可能需要很长时间。(有关更多详细信息,请参阅保证交付。 )
Messaging practically guarantees that the Message will eventually be delivered to the receiver. What it cannot guarantee is how long the delivery may take. For example, if the network connecting the sender and receiver is down for a week, then it could take a week to deliver a message. Messaging is highly reliable, even when the participants (sender, network, and receiver) are not, but messages can take a very long time to transmit in unreliable circumstances. (For more details, see Guaranteed Delivery.)
通常,消息内容的有用时间有实际限制。如果发出股票报价请求的呼叫者在一分钟左右没有收到答复,则可能会失去兴趣。这意味着请求的传输时间不应超过一分钟,而且答案最好很快传回。超过一两分钟的股票报价回复可能太旧了,因此无关紧要。
Often, a message's contents have a practical limit for how long they're useful. A caller issuing a stock quote request probably loses interest if it does not receive an answer within a minute or so. That means the request should not take more than a minute to transmit but also that the answer had better transmit back very quickly. A stock quote reply more than a minute or two old is probably too old and therefore irrelevant.
一旦发送者发送消息而没有得到回复,则无法取消或撤回该消息。同样,接收者可以检查消息何时发送,如果消息太旧则拒绝该消息,但是不同情况下的不同发送者对于多长才算太长可能有不同的想法,那么接收者如何知道要拒绝哪些消息呢?发送者需要一种指定消息生命周期的方法。
Once the sender sends a message and does not get a reply, it has no way to cancel or recall the message. Likewise, a receiver could check when a message was sent and reject the message if it's too old, but different senders under different circumstances may have different ideas about how long is too long, so how does the receiver know which messages to reject? What is needed is a way for the sender to specify the message's lifetime.
|
设置消息过期时间以指定消息有效的时间限制。 Set the Message Expiration to specify a time limit for how long the message is viable. |
一旦消息的有效时间过去,并且该消息仍未被消费,则该消息将过期。消息系统的消费者将忽略过期的消息;他们对待该消息就好像它从未发送过一样。大多数消息系统实现将过期消息重新路由到死信通道,而其他系统则简单地丢弃过期消息;这可能是可配置的。
Once the time for which a message is viable passes, and the message still has not been consumed, then the message will expire. The messaging system's consumers will ignore an expired message; they treat the message as if it where never sent in the first place. Most messaging system implementations reroute expired messages to the Dead Letter Channel, whereas others simply discard expired messages; this may be configurable.
消息过期就像牛奶盒上的过期日期。在那之后,你就不应该喝牛奶了。同样,当消息过期时,消息传递系统不应再传递它。如果接收者仍然收到消息但无法在过期之前处理它,则接收者应该丢弃该消息。
A Message Expiration is like the expiration date on a milk carton. After that date, you shouldn't drink the milk. Likewise, when a message expires, the messaging system should no longer deliver it. If a receiver still receives a message but cannot process it before the expiration, the receiver should throw away the message.
消息过期是一个时间戳(日期和时间),指定消息的生存时间或过期时间。该设置可以用相对或绝对术语来指定。绝对设置指定消息过期的日期和时间。相对设置指定消息在过期之前应存活多长时间;消息系统会根据消息发送的时间将相对设置转换为绝对设置。消息传递系统负责调整与发送者不同时区的接收者的时间戳、调整夏令时以及任何其他可能导致两个不同时钟无法就时间达成一致的问题。
A Message Expiration is a timestamp (date and time) that specifies how long the message will live or when it will expire. The setting can be specified in relative or absolute terms. An absolute setting specifies a date and time when the message will expire. A relative setting specifies how long the message should live before it expires; the messaging system will use the time when the message is sent to convert the relative setting into an absolute one. The messaging system is responsible for adjusting the timestamp for receivers in different time zones from the sender, for adjustments in daylight savings times, and any other issues that can keep two different clocks from agreeing on what time it is.
消息过期属性有一个相关属性,发送时间,它指定消息的发送时间。消息的绝对过期时间戳必须晚于其发送时间戳(否则消息将立即过期)。为了避免这个问题,发送者通常指定相对的过期时间,在这种情况下,消息系统通过将相对超时时间添加到发送时间戳来计算过期时间戳(过期时间=发送时间+生存时间)。
The message expiration property has a related property, sent time, which specifies when the message was sent. A message's absolute expiration timestamp must be later than its sent timestamp (or else the message will expire immediately). To avoid this problem, senders usually specify expiration times relatively, in which case the messaging system calculates the expiration timestamp by adding the relative timeout to the sent timestamp (expiration time = sent time + time to live).
当消息过期时,消息传递系统可以简单地丢弃它或者可以将其移动到死信通道。发现自己拥有过期消息的接收者应将其移至无效消息通道。通过发布-订阅通道,每个订阅者都会获得自己的消息副本;消息的某些副本可能会成功到达其订阅者,而同一消息的其他副本在其订阅者使用它们之前就过期了。使用请求-答复时,带有过期设置的回复消息可能无法正常工作,如果回复过期,请求的发送者将永远不会知道该请求是否曾经被接收过。如果使用回复过期,则请求发送方必须设计为能够处理从未收到预期回复的情况。
When a message expires, the messaging system may simply discard it or may move it to a Dead Letter Channel. A receiver that finds itself in possession of an expired message should move it to the Invalid Message Channel. With a Publish-Subscribe Channel, each subscriber gets its own copy of the message; some copies of a message may reach their subscribers successfully, whereas other copies of the same message expire before their subscribers consume them. When using Request-Reply, a reply message with an expiration setting may not work wellif the reply expires, the sender of the request will never know whether the request was ever received in the first place. If reply expirations are used, the request sender has to be designed to handle the case where expected replies are never received.
|
示例: JMS 生存时间参数 Example: JMS Time-to-Live Parameter 消息过期是 JMS 规范所说的“消息生存时间”[ JMS 1.1 ]、[ Hapner ]。JMS 消息具有用于消息过期的预定义属性JMSExpiration ,但发送方不应通过Message.setJMSExpiration(long) 设置它,因为JMS 提供程序将在发送消息时覆盖该设置。相反,发送者应该使用其MessageProducer( QueueSender或TopicPublisher )来设置其发送的所有消息的超时;此设置的方法是MessageProducer.setTimeToLive(long)。发送者还可以使用MessageProducer.send(Message message, int DeliveryMode, intpriority, long timeToLive)方法设置单个消息的生存时间,其中第四个参数是以毫秒为单位的生存时间。生存时间是一个相对设置,指定消息发送后多长时间应过期。 Message expiration is what the JMS specification calls "message time-to-live" [JMS 1.1], [Hapner]. JMS messages have a predefined property for message expiration, JMSExpiration, but a sender should not set it via Message.setJMSExpiration(long) because the JMS provider will override that setting when the message is sent. Rather, the sender should use its MessageProducer (QueueSender or TopicPublisher) to set the timeout for all messages it sends; the method for this setting is MessageProducer.setTimeToLive(long). A sender can also set the time-to-live on an individual message using the MessageProducer.send(Message message, int deliveryMode, int priority, long timeToLive) method, where the fourth parameter is the time-to-live in milliseconds. Time-to-live is a relative setting specifying how long after the message is sent it should expire. |
|
示例: .NET 接收时间和到达队列时间属性 Example: .NET Time-to-Be-Received and Time-to-Reach-Queue Properties .NET 消息有两个用于指定过期时间的属性:TimeToBeReceived和TimeToReachQueue 。reach-queue 设置指定消息必须到达其目标队列的时间,之后消息可能会无限期地位于队列中。be-received 设置指定接收方必须消耗消息的时间,这限制了将消息传输到目标队列的总时间加上消息在目标队列上可以花费的时间。TimeToBeReceived相当于 JMS 的JMSExpiration属性。两个时间设置都有一个System.TimeSpan类型的值,即时间长度 [ SysMsg],[迪克曼]。 A .NET Message has two properties for specifying expiration: TimeToBeReceived and TimeToReachQueue. The reach-queue setting specifies how long the message has to reach its destination queue, after which the message might sit in the queue indefinitely. The be-received setting specifies how long the message has to be consumed by a receiver, which limits the total time for transmitting the message to its destination queue plus the amount of time the message can spend sitting on the destination queue. TimeToBeReceived is equivalent to JMS's JMSExpiration property. Both time settings have a value of type System.TimeSpan, a length of time [SysMsg], [Dickman]. |
通过消息进行通信的多个应用程序遵循商定的数据格式,也许是企业范围的规范数据模型。但是,该格式可能需要随着时间的推移而改变。
Several applications, communicating via Messages, follow an agreed-upon data format, perhaps an enterprise wide Canonical Data Model. However, that format may need to change over time.
|
如何设计消息的数据格式以适应未来可能的变化? How can a message's data format be designed to allow for possible future changes? |
即使您设计了适用于所有参与应用程序的数据格式,未来的需求也可能会发生变化。可能会添加具有新格式要求的新应用程序,可能需要将新数据添加到消息中,或者开发人员可能会找到更好的方法来构造相同的数据。不管怎样,设计一个单一的企业数据模型已经够困难的了。设计一个未来永远不需要改变的产品几乎是不可能的。
Even when you design a data format that works for all participating applications, future requirements may change. New applications may be added that have new format requirements, new data may need to be added to the messages, or developers may find better ways to structure the same data. Whatever the case, designing a single enterprise data model is difficult enough; designing one that will never need to change in the future is darn near impossible.
当企业的数据格式发生变化时,如果所有的应用程序都随之改变,那是没有问题的。如果每个应用程序都停止使用旧格式并开始使用新格式,并且所有应用程序同时执行此操作,那么转换就会很简单。问题在于,某些应用程序会先于其他应用程序进行转换,而一些较少使用的应用程序可能根本不会转换。即使所有应用程序可以同时转换,也必须消耗所有消息,以便在转换发生之前所有通道都是空的。
When an enterprise's data format changes, there would be no problem if all of the applications changed with it. If every application stopped using the old format and started using the new format, and all did so at exactly the same time, then conversion would be simple. The problem is that some applications will be converted before others, while some less-used applications may never be converted at all. Even if all applications could be converted at the same time, all messages would have to be consumed so that all channels are empty before the conversion could occur.
实际上,应用程序必须能够同时支持旧格式和新格式。为此,应用程序必须能够区分哪些消息遵循旧格式,哪些消息遵循新格式。
Realistically, applications will have to be able to support the old format and the new format simultaneously. To do this, applications must be able to tell which messages follow the old format and which follow the new.
一种解决方案可能是对新格式的消息使用一组单独的通道。然而,这将导致大量通道、重复设计和配置复杂性,因为每个应用程序都必须针对不断扩展的通道种类进行配置。
One solution might be to use a separate set of channels for the messages with the new format. That, however, would lead to a huge number of channels, duplication of design, and configuration complexity as each application has to be configured for an ever-expanding assortment of channels.
更好的解决方案是让新格式的消息使用旧格式消息所使用的相同通道。这意味着接收者需要一种方法来区分使用同一通道的不同格式的消息。每条消息都必须指定它所使用的格式,并且需要一种简单的方法来指示其格式。
A better solution is for the messages with the new format to use the same channels that the old format messages are using. This means that receivers need a way to distinguish messages of different formats that are using the same channel. Each message must specify what format it is using, and it needs a simple way to indicate its format.
|
设计包含格式指示符的数据格式,以便消息指定它正在使用的格式。 Design a data format that includes a Format Indicator so that the message specifies what format it is using. |
格式指示符使发送者能够告诉接收者消息的格式。这样,期望多种可能格式的接收者就知道消息正在使用哪一种格式,从而知道如何解释消息的内容。
The Format Indicator enables the sender to tell the receiver the format of the message. This way, a receiver expecting several possible formats knows which one a message is using and therefore how to interpret the message's contents.
实现格式指示器有三种主要替代方案:
There are three main alternatives for implementing a Format Indicator:
版本号 唯一标识格式的数字或字符串。发送方和接收方必须就特定指示符指定的格式达成一致。这种方法的优点是发送者和接收者不必就格式描述符的共享存储库达成一致,但缺点是每个人都必须知道指示什么描述符以及在哪里访问它。
Version Number A number or string that uniquely identifies the format. Both the sender and receiver must agree on which format is designated by a particular indicator. The advantage of this approach is that the sender and receiver do not have to agree on a shared repository for format descriptors, but the drawback is that each must know what descriptor is indicated and where to access it.
外键 唯一的 ID,例如文件名、数据库行键、主键或指定格式文档的 Internet URL。发送者和接收者必须就键到文档的映射以及模式文档的格式达成一致。这种方法的优点是外键非常紧凑,可以指向共享存储库中的详细数据格式描述。主要缺点在于每个消息传递参与者必须从潜在的远程资源检索格式文档。
Foreign Key A unique IDsuch as a filename, a database row key, a home primary key, or an Internet URLthat specifies a format document. The sender and receiver must agree on the mapping of keys to documents and the format of the schema document. The advantage of this approach is that the foreign key is very compact and can point to a detailed data format description in a shared repository. The main drawback lies in the fact that each messaging participant has to retrieve the format document from a potentially remote resource.
格式文档 描述数据格式的 模式。架构文档不必通过外键检索或从版本号推断;它嵌入在消息中。发送者和接收者必须就模式的格式达成一致。这种替代方案的优点是消息是独立的。然而,消息流量增加,因为每条消息都携带很少改变的格式信息。
Format Document A schema that describes the data format. The schema document does not have to be retrieved via a foreign key or inferred from a version number; it is embedded in the message. The sender and the receiver must agree on the format of the schema. The advantage of this alternative is that messages are self-contained. However, message traffic increases because each message carries format information that rarely changes.
版本号或外键可以存储在发送者和接收者同意的标头字段中。对格式版本不感兴趣的接收者可以忽略该字段。格式文档可能太长或太复杂,无法存储在标头字段中,在这种情况下,消息正文必须具有包含两部分的格式:架构和数据。
A version number or foreign key can be stored in a header field that the senders and receivers agree upon. Receivers that are not interested in the format version can ignore the field. A format document may be too long or complex to store in a header field, in which case the message body must have a format that contains two parts: the schema and the data.
|
示例: XML Example: XML XML 文档提供了所有三种方法的示例。一个示例是 XML 声明,如下所示: XML documents have examples of all three approaches. One example is an XML declaration, like this: <?xml 版本=“1.0”?> <?xml version="1.0"?> 这里,1.0是版本号,指示文档是否符合该版本的 XML 规范。另一个例子是文档类型声明,它可以采用两种形式。它可以是包含系统标识符的外部 ID,如下所示: Here, 1.0 is a version number that indicates the document's conformance to that version of the XML specification. Another example is the document type declaration, which can take two forms. It can be an external ID containing a system identifier, like this: <!DOCTYPE问候系统“hello.dtd”> <!DOCTYPE greeting SYSTEM "hello.dtd"> 系统标识符hello.dtd是一个外键,指示包含描述此 XML 文档格式的 DTD 文档的文件。该声明也可以包含在本地,如下所示: The system identifier, hello.dtd, is a foreign key that indicates the file containing the DTD document that describes this XML document's format. The declaration can also be included locally, like this: <!DOCTYPE 问候语 [ <!元素问候语 (#PCDATA)> ]> <!DOCTYPE greeting [ <!ELEMENT greeting (#PCDATA)> ]> 标记声明[<!ELEMENTgreeting (#PCDATA)>]是一个格式文档,是一个描述 XML 格式 [ XML 1.0 ] 的嵌入式架构文档。 The markup declaration, [<!ELEMENT greeting (#PCDATA)>], is a format document, an embedded schema document that describes the XML's format [XML 1.0]. |
到目前为止,我们已经介绍了很多模式。我们已经了解了基本的消息传递组件,例如Message Channel 、 Message和Message Endpoint 。我们还看到了消息传递通道和消息构建的详细模式。那么,所有这些模式如何组合在一起呢?开发人员如何使用这些模式集成应用程序?代码是什么样的,它是如何工作的?
So far, we've introduced a lot of patterns. We've seen the basic messaging components, such as Message Channel, Message, and Message Endpoint. We've also seen detailed patterns for messaging channels and for message construction. So, how do all of these patterns fit together? How does a developer integrate applications using these patterns? What does the code look like, and how does it work?
这是我们真正看到代码的章节。我们有两个例子:
This is the chapter where we really get to see the code. We have two examples:
请求-回复 演示(在 Java 和 .NET/C# 中)如何使用消息传递发送请求消息并使用回复消息进行响应。
Request-Reply Demonstrates (in Java and .NET/C#) how to use messaging to send a request message and respond with a reply message.
发布-订阅 探索如何使用 JMS 主题来实现观察者 [ GoF ]模式。
Publish-Subscribe Explores how to use a JMS Topic to implement the Observer [GoF] pattern.
这两个简单的示例应该可以帮助您开始向自己的应用程序添加消息传递。
These two simple examples should get you started on adding messaging to your own applications.
这是一个简单但功能强大的示例,传输请求并传回回复。它由两个主要类组成:
This is a simple but powerful example, transmitting a request and transmitting back a reply. It consists of two main classes:
请求者发送请求消息并期望接收回复消息的对象。
Requestor The object that sends the request message and expects to receive the reply message.
Replier 接收请求消息并发送回复消息作为响应的对象。
Replier The object that receives the request message and sends a reply message in response.
这两个发送简单消息的简单类说明了多种模式:
These two simple classes sending simple messages illustrate a number of the patterns:
Message Channel and Point-to-Point Channel One channel for transmitting the requests, another for transmitting the replies.
文档 消息
Document Message The default type of message, used as both the request and the reply.
请求-答复 通过一对通道发送的一对消息,允许两个应用程序进行双向对话。
Request-Reply A pair of messages sent over a pair of channels, allowing the two applications to have a two-way conversation.
返回 地址
Return Address The channel to send the response on.
相关标识符 引起此响应的请求的 ID。
Correlation Identifier The ID of the request that caused this response.
Datatype Channel All of the messages on each channel should be of the same type.
Invalid Message Channel What happens to messages that aren't of the right type.
示例代码还演示了第 10 章“消息传递端点”中的一些模式:
The example code also demonstrates a couple of patterns from Chapter 10, "Messaging Endpoints":
轮询消费者 请求者 如何
Polling Consumer How the requestor consumes reply messages.
事件驱动的消费者 回复者 如何
Event-Driven Consumer How the replier consumes request messages.
虽然本书是技术、产品和语言中立的,但代码却不能,所以我们选择了两个消息传递编程平台来实现这个示例:
While this book is technology-, product-, and language-neutral, code cannot be, so we've chosen two messaging programming platforms to implement this example:
Java J2EE 中的 JMS API
The JMS API in Java J2EE
Microsoft .NET 中使用 C# 的 MSMQ API
The MSMQ API in Microsoft .NET using C#
两个平台都实现了相同的请求-应答示例。选择您最喜欢的平台作为消息传递工作原理的示例。如果您想了解消息传递在其他平台上的工作原理,但不知道如何为该平台编写代码,您可以通过将其与您已知的语言中的代码进行比较来找出答案。
The same request-reply example is implemented in both platforms. Choose your favorite platform as an example of how messaging works. If you'd like to see how messaging works on the other platform but don't know how to write code for that platform, you can figure it out by comparing it to the code in the language you already know.
此示例探讨如何使用发布-订阅通道实现观察者模式。 它考虑了分发和线程问题,并讨论了消息传递如何极大地简化这些问题。该示例展示了如何实现通知的推送和拉取模型,并比较了每种模型的结果。它还探讨了如何设计复杂企业所需的一套适当的渠道,该企业有众多主题通知众多观察者。
This example explores how to implement the Observer pattern using a Publish-Subscribe Channel. It considers distribution and threading issues and discusses how messaging greatly simplifies these issues. The example shows how to implement both the push and pull models of notification and compares the consequences of each. It also explores how to design an adequate set of channels needed for a complex enterprise with numerous subjects notifying numerous observers.
讨论和示例代码说明了几种模式:
The discussion and sample code illustrate several patterns:
发布-订阅 通道
Publish-Subscribe Channel The channel that provides publish-subscribe notification.
事件 消息
Event Message The message type used to send notifications.
请求-回复 该技术用作拉模型的一部分,供观察者向主体请求状态。
Request-Reply The technique used as part of the pull model for an observer to request the state from the subject.
命令 消息
Command Message The message type used by an observer to request the state from the subject.
文档 消息
Document Message The message type used by a subject to send its state to an observer.
返回地址 告诉
Return Address Tells the subject how to send the state to the observer.
数据类型 Channel
Datatype Channel The main guideline for whether two unrelated subjects can use the same channel to update the same group of observers.
示例代码还演示了第 10 章“消息传递端点”中的一些模式:
The example code also demonstrates a couple of patterns from Chapter 10, "Messaging Endpoints":
Messaging Gateway How the subject and observer encapsulate the messaging code so that they are not messaging-specific.
事件驱动的消费者观察者 如何
Event-Driven Consumer How the observers consume notification messages.
持久订阅者 不想错过通知的观察者,即使在发送通知时观察者暂时断开连接。
Durable Subscriber An observer that does not want to miss notifications, even if the observer is temporarily disconnected when the notification is sent.
此示例是使用 JMS 在 Java 中实现的,因为 JMS 通过其 Topic 接口支持发布-订阅通道作为API的显式功能。.NET 不为在 MSMQ 中使用发布-订阅语义提供类似级别的支持。如果确实如此,JMS 示例中的技术也应该很容易适用于 .NET 程序。
This example is implemented in Java using JMS because JMS supports Publish-Subscribe Channel as an explicit feature of the API through its Topic interface. .NET does not provide a similar level of support for using the publish-subscribe semantics in MSMQ. When it does, the techniques in the JMS example should be readily applicable to .NET programs as well.
这是如何使用消息传递的简单示例,在 JMS [ JMS ] 中实现。它展示了如何实现Request-Reply,其中请求者应用程序发送请求,回复者应用程序接收请求并返回回复,然后请求者接收回复。它还显示了如何将无效消息重新路由到特殊通道。
This is a simple example of how to use messaging, implemented in JMS [JMS]. It shows how to implement Request-Reply, where a requestor application sends a request, a replier application receives the request and returns a reply, and the requestor receives the reply. It also shows how an invalid message will be rerouted to a special channel.
请求-答复示例的组成部分
Components of the Request-Reply Example
该示例是使用 JMS 1.1 开发的,并使用 J2EE 1.4 参考实现运行。
This example was developed using JMS 1.1 and run using the J2EE 1.4 reference implementation.
该示例由两个主要类组成:
This example consists of two main classes:
请求者发送请求消息并等待接收回复消息作为响应的 消息。
Requestor A Message Endpoint that sends a request message and waits to receive a reply message as a response.
Replier等待接收请求消息的消息端点 ;当它出现时,它会通过发送回复消息进行响应。
Replier A Message Endpoint that waits to receive the request message; when it does, it responds by sending the reply message.
请求者和回复者各自运行在单独的 Java 虚拟机 (JVM) 中,这使得通信变得分布式。
The requestor and the replier each run in a separate Java Virtual Machine (JVM), which is what makes the communication distributed.
此示例假设消息传递系统定义了以下三个队列:
This example assumes that the messaging system has these three queues defined:
jms/ RequestQueue请求者用来将请求消息发送到回复者的队列。
jms/RequestQueue The queue the requestor uses to send the request message to the replier.
jms/ReplyQueue 回复者用来向请求者发送回复消息的队列。
jms/ReplyQueue The queue the replier uses to send the reply message to the requestor.
jms/InvalidMessages 请求者和回复者收到无法解释的消息时将消息移至的 队列。
jms/InvalidMessages The queue to which the requestor and replier move a message when they receive a message that they cannot interpret.
该示例的工作原理如下。当请求程序在命令行窗口中启动时,它会启动并打印如下输出:
Here's how the example works. When the requestor is started in a command-line window, it starts and prints output like this:
发送请求
时间:1048261736520 毫秒
消息ID: ID:_XYZ123_1048261766139_6.2.1.1
科雷尔。身份证号:空
回复:com.sun.jms.Queue:jms/ReplyQueue
内容:世界你好。
Sent request
Time: 1048261736520 ms
Message ID: ID:_XYZ123_1048261766139_6.2.1.1
Correl. ID: null
Reply to: com.sun.jms.Queue: jms/ReplyQueue
Contents: Hello world.
这表明请求者已经发送了请求消息。请注意,即使应答器甚至没有运行并因此无法接收请求,这仍然有效。
This shows that the requestor has sent a request message. Notice that this works even though the replier isn't even running and therefore cannot receive the request.
当回复器在另一个命令行窗口中启动时,它会启动并打印如下输出:
When the replier is started in another command-line window, it starts and prints output like this:
收到请求
时间:1048261766790 毫秒
消息ID: ID:_XYZ123_1048261766139_6.2.1.1
科雷尔。身份证号:空
回复:com.sun.jms.Queue:jms/ReplyQueue
内容:世界你好。
已发送回复
时间:1048261766850 毫秒
消息ID: ID:_XYZ123_1048261758148_5.2.1.1
科雷尔。ID: ID:_XYZ123_1048261766139_6.2.1.1
回复:无
内容:世界你好。
Received request
Time: 1048261766790 ms
Message ID: ID:_XYZ123_1048261766139_6.2.1.1
Correl. ID: null
Reply to: com.sun.jms.Queue: jms/ReplyQueue
Contents: Hello world.
Sent reply
Time: 1048261766850 ms
Message ID: ID:_XYZ123_1048261758148_5.2.1.1
Correl. ID: ID:_XYZ123_1048261766139_6.2.1.1
Reply to: null
Contents: Hello world.
这表明回复者收到了请求消息并发送了回复消息。
This shows that the replier received the request message and sent a reply message.
此输出中有几个有趣的项目。首先,注意请求发送和接收的时间戳;请求在发送后被接收(30270 毫秒后)。其次,请注意,这两种情况下的消息 ID 相同,因为它是同一条消息。第三,注意内容Hello world是相同的,这非常好,因为这是正在传输的数据,并且双方必须相同。(此示例中的请求非常蹩脚。它基本上是一条Document Message ;真正的请求通常是一条Command Message 。)第四,名为 jms/ ReplyQueue的队列已在请求消息中指定为回复消息的目的地(退货地址的示例)。
There are several interesting items in this output. First, notice the request sent and received timestamps; the request was received after it was sent (30270 ms later). Second, notice that the message ID is the same in both cases, because it's the same message. Third, notice that the contents, Hello world, are the same, which is very good because this is the data being transmitted, and it must be the same on both sides. (The request in this example is pretty lame. It is basically a Document Message; a real request would usually be a Command Message.) Fourth, the queue named jms/ReplyQueue has been specified in the request message as the destination for the reply message (an example of Return Address).
接下来,我们将接收请求的输出与发送回复的输出进行比较。首先,请注意,直到收到请求后(60 毫秒后)才发送回复。其次,回复的消息ID与请求的消息ID不同;这是因为请求消息和回复消息是不同的、单独的消息。第三,请求的内容已被提取并添加到回复中(在本示例中,回复器充当简单的“回显”服务)。第四,回复目的地未指定,因为不需要回复(回复不使用Return Address )。第五,回复的相关 ID 与请求的消息 ID 相同(回复确实使用Correlation Identifier ) 。
Next, let's compare the output from receiving the request to that for sending the reply. First, notice the reply was not sent until after the request was received (60 ms after). Second, the message ID for the reply is different from that for the request; this is because the request and reply messages are different, separate messages. Third, the contents of the request have been extracted and added to the reply (in this example, the replier acts as a simple "echo" service). Fourth, the reply-to destination is unspecified because no reply is expected (the reply does not use Return Address). Fifth, the reply's correlation ID is the same as the request's message ID (the reply does use Correlation Identifier).
最后,回到第一个窗口,请求者收到以下回复:
Finally, back in the first window, the requestor received the following reply:
收到回复
时间:1048261797060 毫秒
消息ID: ID:_XYZ123_1048261758148_5.2.1.1
科雷尔。ID: ID:_XYZ123_1048261766139_6.2.1.1
回复:无
内容:世界你好。
Received reply
Time: 1048261797060 ms
Message ID: ID:_XYZ123_1048261758148_5.2.1.1
Correl. ID: ID:_XYZ123_1048261766139_6.2.1.1
Reply to: null
Contents: Hello world.
此输出包含几个感兴趣的项目。发送后收到回复(30210 ms)。回复的消息ID在接收时和发送时是相同的,这证明确实是同一条消息。接收到的消息内容与发送的消息内容相同,相关ID告诉请求者这个回复是针对哪个请求的( Correlation Identifier) 。
This output contains several items of interest. The reply was received after it was sent (30210 ms). The message ID of the reply was the same when it was received as when it was sent, which proves that it is indeed the same message. The message contents received are the same as those sent, and the correlation ID tells the requestor which request this reply is for (Correlation Identifier).
还要注意,请求者被设计为简单地发送请求、接收答复并退出。因此,收到回复后,请求者不再运行。另一方面,应答器不知道何时会收到请求,因此它永远不会停止运行。要停止它,我们进入其命令 shell 窗口并按回车键,这会导致回复程序退出。
Notice too that the requestor is designed to simply send a request, receive a reply, and exit. So, having received the reply, the requestor is no longer running. The replier, on the other hand, doesn't know when it might receive a request, so it never stops running. To stop it, we go to its command shell window and press the return key, which causes the replier program to exit.
这就是 JMS 请求-答复示例。请求者已准备并发送请求。回复者收到请求并发送回复。然后,请求者收到了对其原始请求的答复。
So, that's the JMS request-reply example. A request was prepared and sent by the requestor. The replier received the request and sent a reply. Then, the requestor received the reply to its original request.
首先我们看一下请求者是如何实现的:
First, let's take a look at how the requestor is implemented:
导入 javax.jms.Connection;
导入 javax.jms.Destination;
导入 javax.jms.JMSException;
导入javax.jms.Message;
导入 javax.jms.MessageConsumer;
导入 javax.jms.MessageProducer;
导入javax.jms.Session;
导入 javax.jms.TextMessage;
导入 javax.naming.NamingException;
公共类请求者{
私人会议;
私有目的地回复队列;
私有 MessageProducer requestProducer;
私人消息消费者回复消费者;
私有 MessageProducer 无效生产者;
受保护的请求者() {
极好的();
}
公共静态请求者newRequestor(连接连接,字符串requestQueueName,
字符串回复队列名称、字符串无效队列名称)
抛出 JMSException、NamingException {
请求者 请求者 = new Requestor();
requestor.initialize(连接,requestQueueName,replyQueueName,invalidQueueName);
退货请求者;
}
protected void 初始化(连接连接,字符串请求队列名称,
字符串回复队列名称、字符串无效队列名称)
抛出 NamingException、JMSException {
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
目标 requestQueue = JndiUtil.getDestination(requestQueueName);
回复队列 = JndiUtil.getDestination(replyQueueName);
目标无效队列 = JndiUtil.getDestination(invalidQueueName);
requestProducer = session.createProducer(requestQueue);
回复消费者 = session.createConsumer(replyQueue);
invalidProducer = session.createProducer(invalidQueue);
}
公共无效发送()抛出JMSException {
TextMessage requestMessage = session.createTextMessage();
requestMessage.setText("你好世界。");
requestMessage.setJMSReplyTo(replyQueue);
requestProducer.send(requestMessage);
System.out.println("已发送请求");
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\t消息 ID: " + requestMessage.getJMSMessageID());
System.out.println("\tCorrel.ID: " + requestMessage.getJMSCorrelationID());
System.out.println("\t回复:" + requestMessage.getJMSReplyTo());
System.out.println("\t内容: " + requestMessage.getText());
}
公共无效 receiveSync() 抛出 JMSException {
消息msg=replyConsumer.receive();
if (msg 文本消息实例) {
TextMessage回复消息 = (TextMessage) msg;
System.out.println("收到回复");
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\t消息 ID: " +replyMessage.getJMSMessageID());
System.out.println("\tCorrel.ID: " +replyMessage.getJMSCorrelationID());
System.out.println("\t回复:" +replyMessage.getJMSReplyTo());
System.out.println("\t内容: " +replyMessage.getText());
} 别的 {
System.out.println("检测到无效消息");
System.out.println("\tType: " + msg.getClass().getName());
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\t消息 ID: " + msg.getJMSMessageID());
System.out.println("\tCorrel.ID: " + msg.getJMSCorrelationID());
System.out.println("\t回复:" + msg.getJMSReplyTo());
msg.setJMSCorrelationID(msg.getJMSMessageID());
无效生产者.send(msg);
System.out.println("发送到无效消息队列");
System.out.println("\tType: " + msg.getClass().getName());
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\t消息 ID: " + msg.getJMSMessageID());
System.out.println("\tCorrel.ID: " + msg.getJMSCorrelationID());
System.out.println("\t回复:" + msg.getJMSReplyTo());
}
}
}
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.NamingException;
public class Requestor {
private Session session;
private Destination replyQueue;
private MessageProducer requestProducer;
private MessageConsumer replyConsumer;
private MessageProducer invalidProducer;
protected Requestor() {
super();
}
public static Requestor newRequestor(Connection connection, String requestQueueName,
String replyQueueName, String invalidQueueName)
throws JMSException, NamingException {
Requestor requestor = new Requestor();
requestor.initialize(connection, requestQueueName, replyQueueName, invalidQueueName);
return requestor;
}
protected void initialize(Connection connection, String requestQueueName,
String replyQueueName, String invalidQueueName)
throws NamingException, JMSException {
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination requestQueue = JndiUtil.getDestination(requestQueueName);
replyQueue = JndiUtil.getDestination(replyQueueName);
Destination invalidQueue = JndiUtil.getDestination(invalidQueueName);
requestProducer = session.createProducer(requestQueue);
replyConsumer = session.createConsumer(replyQueue);
invalidProducer = session.createProducer(invalidQueue);
}
public void send() throws JMSException {
TextMessage requestMessage = session.createTextMessage();
requestMessage.setText("Hello world.");
requestMessage.setJMSReplyTo(replyQueue);
requestProducer.send(requestMessage);
System.out.println("Sent request");
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\tMessage ID: " + requestMessage.getJMSMessageID());
System.out.println("\tCorrel. ID: " + requestMessage.getJMSCorrelationID());
System.out.println("\tReply to: " + requestMessage.getJMSReplyTo());
System.out.println("\tContents: " + requestMessage.getText());
}
public void receiveSync() throws JMSException {
Message msg = replyConsumer.receive();
if (msg instanceof TextMessage) {
TextMessage replyMessage = (TextMessage) msg;
System.out.println("Received reply ");
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\tMessage ID: " + replyMessage.getJMSMessageID());
System.out.println("\tCorrel. ID: " + replyMessage.getJMSCorrelationID());
System.out.println("\tReply to: " + replyMessage.getJMSReplyTo());
System.out.println("\tContents: " + replyMessage.getText());
} else {
System.out.println("Invalid message detected");
System.out.println("\tType: " + msg.getClass().getName());
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\tMessage ID: " + msg.getJMSMessageID());
System.out.println("\tCorrel. ID: " + msg.getJMSCorrelationID());
System.out.println("\tReply to: " + msg.getJMSReplyTo());
msg.setJMSCorrelationID(msg.getJMSMessageID());
invalidProducer.send(msg);
System.out.println("Sent to invalid message queue");
System.out.println("\tType: " + msg.getClass().getName());
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\tMessage ID: " + msg.getJMSMessageID());
System.out.println("\tCorrel. ID: " + msg.getJMSCorrelationID());
System.out.println("\tReply to: " + msg.getJMSReplyTo());
}
}
}
想要发送请求和接收回复的应用程序可以使用请求者来执行此操作。应用程序为其请求者提供与消息传递系统的连接。它还指定了三个队列的 JNDI 名称:请求队列、回复队列和无效消息队列。这是请求者初始化自身所需的信息。
An application that wants to send requests and receive replies could use a requestor to do so. The application provides its requestor a connection to the messaging system. It also specifies the JNDI names of three queues: the request queue, the reply queue, and the invalid message queue. This is the information the requestor needs to initialize itself.
在初始化方法中,请求者使用连接和队列名称连接到消息传递系统。
In the initialize method, the requestor uses the connection and queue names to connect to the messaging system.
它使用连接来创建会话。应用程序只需要与消息传递系统的一个连接,但应用程序中希望独立发送和接收消息的每个组件都需要自己的会话。两个线程不能共享一个会话;他们应该各自使用不同的会话,以便会话正常工作。
It uses the connection to create a session. An application needs only one connection to a messaging system, but each component in the application that wishes to send and receive messages independently needs its own session. Two threads cannot share a single session; they should each use a different session so that the sessions will work properly.
它使用队列名称来查找队列,即Destination 。名称是 JNDI 标识符;JndiUtil执行JNDI 查找。
It uses the queue names to look up the queues, which are Destinations. The names are JNDI identifiers; JndiUtil performs the JNDI lookups.
它创建一个MessageProducer 用于在请求队列上发送消息,一个 MessageConsumer 用于从回复队列接收消息,以及另一个生产者用于将消息移动到无效消息队列。
It creates a MessageProducer for sending messages on the request queue, a MessageConsumer for receiving messages from the reply queue, and another producer for moving messages to the invalid message queue.
请求者必须能够做的一件事是发送请求消息。为此,它实现了send()方法。
One thing that the requestor must be able to do is send request messages. For that, it implements the send() method.
它创建一条TextMessage 并将其内容设置为“Hello world”。
It creates a TextMessage and sets its contents to "Hello world."
它将消息的reply-to属性设置为回复队列。这是一个返回地址,它将告诉回复者如何发回回复。
It sets the message's reply-to property to be the reply queue. This is a Return Address that will tell the replier how to send back the reply.
它使用requestProducer 来发送消息。生产者连接到请求队列,因此这是发送消息的队列。
It uses the requestProducer to send the message. The producer is connected to the request queue, so that's the queue the message is sent on.
然后它打印出刚刚发送的消息的详细信息。这是在消息发送后完成的,因为消息 ID 由消息传递系统设置,直到消息实际发送后才设置。
It then prints out the details of the message it just sent. This is done after the message is sent because the message ID is set by the messaging system and is not set until the message is actually sent.
请求者必须能够做的另一件事是接收回复消息。为此,它实现了receiveSync()方法。
The other thing the requestor must be able to do is receive reply messages. It implements the receiveSync() method for this purpose.
它使用其replyConsumer 来接收回复。消费者连接到回复队列,因此它将从那里接收消息。它使用receive()方法来获取消息,该消息会同步阻塞,直到消息被传递到队列并从队列中读取,因此请求者是一个Polling Consumer 。由于此接收是同步的,因此请求者的方法称为receiveSync()。
It uses its replyConsumer to receive the reply. The consumer is connected to the reply queue, so it will receive messages from there. It uses the receive() method to get the message, which synchronously blocks until a message is delivered to the queue and is read from the queue, so the requestor is a Polling Consumer. Because this receive is synchronous, the requestor's method is called receiveSync().
该消息应该是TextMessage 。如果是这样,请求者将获取消息的内容并打印出消息的详细信息。
The message should be a TextMessage. If so, the requestor gets the message's contents and prints out the message's details.
如果消息不是 TextMessage ,则无法处理该消息。请求者不只是丢弃消息,而是将其重新发送到无效消息队列。重新发送消息将更改其消息 ID,因此在重新发送消息之前,请求者将其原始消息 ID 存储在其相关 ID 中(请参阅相关标识符) 。
If the message is not a TextMessage, then the message cannot be processed. Rather than just discarding the message, the requestor resends it to the invalid message queue. Resending the message will change its message ID, so before resending it, the requestor stores its original message ID in its correlation ID (see Correlation Identifier).
通过这种方式,请求者会执行所有必要的操作来发送请求、接收回复,并在消息没有任何意义时将回复路由到特殊队列。(注意:JMS 提供了一个特殊的类QueueRequestor ,其目的是实现一个接收回复的请求程序,就像我们在这里一样。我们自己实现了代码,而不是使用预构建的 JMS 类,以便我们可以向您展示代码是如何工作的。 )
In this way, a requestor does everything necessary to send a request, receive a reply, and route the reply to a special queue if the message does not make any sense. (Note: JMS provides a special class, QueueRequestor, whose purpose is to implement a requestor that receives replies just as we have here. We implemented the code ourselves rather than using a prebuilt JMS class so that we could show you how the code works.)
接下来我们看看replier是如何实现的。
Next, let's take a look at how the replier is implemented.
导入 javax.jms.Connection;
导入 javax.jms.Destination;
导入 javax.jms.JMSException;
导入javax.jms.Message;
导入 javax.jms.MessageConsumer;
导入 javax.jms.MessageListener;
导入 javax.jms.MessageProducer;
导入javax.jms.Session;
导入 javax.jms.TextMessage;
导入 javax.naming.NamingException;
公共类 Replier 实现 MessageListener {
私人会议;
私有 MessageProducer 无效生产者;
受保护的回复者() {
极好的();
}
public static Replier newReplier(连接连接,
字符串请求队列名称、字符串无效队列名称)
抛出 JMSException、NamingException {
回复者回复者 = new Replier();
replier.initialize(连接, requestQueueName, invalidQueueName);
返回回复者;
}
protected void 初始化(连接连接,字符串请求队列名称,
字符串无效队列名称)
抛出 NamingException、JMSException {
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
目标 requestQueue = JndiUtil.getDestination(requestQueueName);
目标无效队列 = JndiUtil.getDestination(invalidQueueName);
MessageConsumer requestConsumer = session.createConsumer(requestQueue);
MessageListener 监听器 = this;
requestConsumer.setMessageListener(监听器);
invalidProducer = session.createProducer(invalidQueue);
}
公共无效onMessage(消息消息){
尝试 {
if ((TextMessage 的消息实例) && (message.getJMSReplyTo() != null)) {
TextMessage requestMessage = (TextMessage) 消息;
System.out.println("收到请求");
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\t消息 ID: " + requestMessage.getJMSMessageID());
System.out.println("\tCorrel.ID: " + requestMessage.getJMSCorrelationID());
System.out.println("\t回复:" + requestMessage.getJMSReplyTo());
System.out.println("\t内容: " + requestMessage.getText());
字符串内容 = requestMessage.getText();
目的地回复Destination = message.getJMSReplyTo();
MessageProducer 回复生产者 = session.createProducer(replyDestination);
TextMessage回复消息 = session.createTextMessage();
回复消息.setText(内容);
replyMessage.setJMSCorrelationID(requestMessage.getJMSMessageID());
回复Producer.send(replyMessage);
System.out.println("已发送回复");
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\t消息 ID: " +replyMessage.getJMSMessageID());
System.out.println("\tCorrel.ID: " +replyMessage.getJMSCorrelationID());
System.out.println("\t回复:" +replyMessage.getJMSReplyTo());
System.out.println("\t内容: " +replyMessage.getText());
} 别的 {
System.out.println("检测到无效消息");
System.out.println("\tType: " + message.getClass().getName());
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\t消息 ID: " + message.getJMSMessageID());
System.out.println("\tCorrel.ID: " + message.getJMSCorrelationID());
System.out.println("\t回复:" + message.getJMSReplyTo());
message.setJMSCorrelationID(message.getJMSMessageID());
invalidProducer.send(消息);
System.out.println("发送到无效消息队列");
System.out.println("\tType: " + message.getClass().getName());
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\t消息 ID: " + message.getJMSMessageID());
System.out.println("\tCorrel.ID: " + message.getJMSCorrelationID());
System.out.println("\t回复:" + message.getJMSReplyTo());
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}
import javax.jms.Connection;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.NamingException;
public class Replier implements MessageListener {
private Session session;
private MessageProducer invalidProducer;
protected Replier() {
super();
}
public static Replier newReplier(Connection connection,
String requestQueueName, String invalidQueueName)
throws JMSException, NamingException {
Replier replier = new Replier();
replier.initialize(connection, requestQueueName, invalidQueueName);
return replier;
}
protected void initialize(Connection connection, String requestQueueName,
String invalidQueueName)
throws NamingException, JMSException {
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination requestQueue = JndiUtil.getDestination(requestQueueName);
Destination invalidQueue = JndiUtil.getDestination(invalidQueueName);
MessageConsumer requestConsumer = session.createConsumer(requestQueue);
MessageListener listener = this;
requestConsumer.setMessageListener(listener);
invalidProducer = session.createProducer(invalidQueue);
}
public void onMessage(Message message) {
try {
if ((message instanceof TextMessage) && (message.getJMSReplyTo() != null)) {
TextMessage requestMessage = (TextMessage) message;
System.out.println("Received request");
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\tMessage ID: " + requestMessage.getJMSMessageID());
System.out.println("\tCorrel. ID: " + requestMessage.getJMSCorrelationID());
System.out.println("\tReply to: " + requestMessage.getJMSReplyTo());
System.out.println("\tContents: " + requestMessage.getText());
String contents = requestMessage.getText();
Destination replyDestination = message.getJMSReplyTo();
MessageProducer replyProducer = session.createProducer(replyDestination);
TextMessage replyMessage = session.createTextMessage();
replyMessage.setText(contents);
replyMessage.setJMSCorrelationID(requestMessage.getJMSMessageID());
replyProducer.send(replyMessage);
System.out.println("Sent reply");
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\tMessage ID: " + replyMessage.getJMSMessageID());
System.out.println("\tCorrel. ID: " + replyMessage.getJMSCorrelationID());
System.out.println("\tReply to: " + replyMessage.getJMSReplyTo());
System.out.println("\tContents: " + replyMessage.getText());
} else {
System.out.println("Invalid message detected");
System.out.println("\tType: " + message.getClass().getName());
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\tMessage ID: " + message.getJMSMessageID());
System.out.println("\tCorrel. ID: " + message.getJMSCorrelationID());
System.out.println("\tReply to: " + message.getJMSReplyTo());
message.setJMSCorrelationID(message.getJMSMessageID());
invalidProducer.send(message);
System.out.println("Sent to invalid message queue");
System.out.println("\tType: " + message.getClass().getName());
System.out.println("\tTime: " + System.currentTimeMillis() + " ms");
System.out.println("\tMessage ID: " + message.getJMSMessageID());
System.out.println("\tCorrel. ID: " + message.getJMSCorrelationID());
System.out.println("\tReply to: " + message.getJMSReplyTo());
}
} catch (JMSException e) {
e.printStackTrace();
}
}
}
应答器是应用程序用来接收请求和发送应答的东西。应用程序向其请求者提供与消息传递系统的连接,以及请求的 JNDI 名称和无效消息队列。(它不需要指定回复队列的名称,因为正如我们将看到的,该名称将由消息的Return Address 提供。 )这是请求者初始化自身所需的信息。
A replier is what an application might use to receive a request and send a reply. The application provides its requestor a connection to the messaging system, as well as the JNDI names of the request and invalid message queues. (It does not need to specify the name of the reply queue because, as we'll see, that will be provided by the message's Return Address.) This is the information the requestor needs to initialize itself.
回复者的初始化代码与请求者的初始化代码非常相似,但存在一些差异。
The replier's initialize code is pretty similar to the requestor's, but there are a couple of differences.
回复者不会查找回复队列并为其创建生产者。这是因为回复者并不认为它总是会在该队列上发送回复;相反,正如我们稍后将看到的,它会让请求消息告诉它在哪个队列上发送回复消息。
The replier does not look up the reply queue and create a producer for it. This is because the replier does not assume it will always send replies on that queue; rather, as we'll see later, it will let the request message tell it what queue to send the reply message on.
回复者是一个事件驱动的消费者,因此它实现了MessageListener。当消息传递到请求队列时,消息系统会自动调用回复者的onMessage方法。
The replier is an Event-Driven Consumer, so it implements MessageListener. When a message is delivered to the request queue, the messaging system will automatically call the replier's onMessage method.
一旦回复者将自己初始化为请求队列上的侦听器,除了等待消息之外,它就没有什么可做的了。与请求者必须显式检查回复队列中的消息不同,回复者是事件驱动的,因此在消息传递系统使用新消息调用其onMessage方法之前不会执行任何操作。该消息将来自请求队列,因为初始化在请求队列上创建了消费者。一旦onMessage收到新消息,它就会像这样处理该消息。
Once the replier has initialized itself to be a listener on the request queue, there's not much for it to do but wait for messages. Unlike the requestor, which has to explicitly check the reply queue for messages, the replier is event-driven and so does nothing until the messaging system calls its onMessage method with a new message. The message will be from the request queue because initialize created the consumer on the request queue. Once onMessage receives a new message, it processes the message like this.
与请求者处理回复消息一样,请求消息应该是 TextMessage 。 它还应该指定发送回复的队列。如果消息不满足这些要求,则回复者会将消息移至无效消息队列(与请求者相同)。
As with the requestor processing a reply message, the request message is supposed to be a TextMessage. It is also supposed to specify the queue on which to send the reply. If the message does not meet these requirements, the replier will move the message to the invalid message queue (same as the requestor).
如果消息满足要求,则回复者将实现其返回地址部分。请记住,请求者设置请求消息的reply-to属性来指定回复队列。回复者现在获取该属性的值并使用它在正确的队列上创建 MessageProducer 。这里重要的部分是回复器没有被硬编码为使用特定的回复队列;它使用每个特定请求消息指定的任何回复队列。
If the message meets the requirements, the replier implements its part of Return Address. Remember that the requestor set the request message's reply-to property to specify the reply queue. The replier now gets that property's value and uses it to create a MessageProducer on the proper queue. The important part here is that the replier is not hard-coded to use a particular reply queue; it uses whatever reply queue each particular request message specifies.
然后回复者创建回复消息。在此过程中,它通过将回复消息的correlation-id属性设置为与请求消息的message-id属性相同的值来实现相关标识符。
The replier then creates the reply message. In doing so, it implements Correlation Identifier by setting the reply message's correlation-id property to the same value as the request message's message-id property.
然后回复者发出回复消息并显示其详细信息。
The replier then sends out the reply message and displays its details.
因此,回复者会执行接收消息(可能是请求)并发送回复所需的一切操作。
Thus, a replier does everything necessary to receive a message (presumably a request) and send a reply.
在我们讨论这个问题时,让我们看一下Invalid Message Channel的示例。请记住,我们需要的队列之一是名为 jms/InvalidMessages 的队列。 其存在是为了如果 JMS 客户端(消息端点)收到它无法处理的消息,它可以将奇怪的消息移动到特殊通道。
While we're at it, let's look at an example of Invalid Message Channel. Remember, one of the queues we need is the one named jms/InvalidMessages. This exists so that if a JMS client (a Message Endpoint) receives a message it cannot process, it can move the strange message to a special channel.
为了演示无效消息处理,我们设计了一个InvalidMessenger 类。该对象专门用于在请求通道上发送格式不正确的消息。请求通道是数据类型通道,因为请求接收者期望请求采用某种格式。无效的信使只是以不同的格式发送消息;当回复者收到消息时,它无法识别消息的格式,因此会将消息移至无效消息队列。
To demonstrate invalid message handling, we have designed an InvalidMessenger class. This object is specifically designed to send a message on the request channel whose format is incorrect. The request channel is a Datatype Channel in that the request receivers expect the requests to be in a certain format. The invalid messenger simply sends a message in a different format; when the replier receives the message, it does not recognize the message's format, so it moves the message to the invalid message queue.
我们将在一个窗口中运行回复程序,并在另一个窗口中运行无效的信使。当无效的信使发送消息时,它会显示如下输出:
We'll run the replier in one window and the invalid messenger in another window. When the invalid messenger sends its message, it displays output like this:
发送无效消息
类型:com.sun.jms.ObjectMessageImpl
时间:1048288516959 毫秒
消息ID: ID:_XYZ123_1048288516639_7.2.1.1
科雷尔。身份证号:空
回复:com.sun.jms.Queue:jms/ReplyQueue
Sent invalid message
Type: com.sun.jms.ObjectMessageImpl
Time: 1048288516959 ms
Message ID: ID:_XYZ123_1048288516639_7.2.1.1
Correl. ID: null
Reply to: com.sun.jms.Queue: jms/ReplyQueue
这表明该消息是 ObjectMessage 的实例(而回复者期望的是TextMessage ) 。应答者收到无效消息后,将其重新发送到无效消息队列中。
This shows that the message is an instance of ObjectMessage (whereas the replier is expecting a TextMessage). The replier receives the invalid message and resends it to the invalid message queue.
检测到无效消息
类型:com.sun.jms.ObjectMessageImpl
时间:1048288517049 毫秒
消息ID: ID:_XYZ123_1048288516639_7.2.1.1
科雷尔。身份证号:空
回复:com.sun.jms.Queue:jms/ReplyQueue
发送到无效消息队列
类型:com.sun.jms.ObjectMessageImpl
时间:1048288517140 毫秒
消息ID: ID:_XYZ123_1048287020267_6.2.1.2
科雷尔。ID: ID:_XYZ123_1048288516639_7.2.1.1
回复:com.sun.jms.Queue:jms/ReplyQueue
Invalid message detected
Type: com.sun.jms.ObjectMessageImpl
Time: 1048288517049 ms
Message ID: ID:_XYZ123_1048288516639_7.2.1.1
Correl. ID: null
Reply to: com.sun.jms.Queue: jms/ReplyQueue
Sent to invalid message queue
Type: com.sun.jms.ObjectMessageImpl
Time: 1048288517140 ms
Message ID: ID:_XYZ123_1048287020267_6.2.1.2
Correl. ID: ID:_XYZ123_1048288516639_7.2.1.1
Reply to: com.sun.jms.Queue: jms/ReplyQueue
一个值得注意的见解是,当消息被移动到无效消息队列时,它实际上是在重新发送,因此它会获得一个新的消息 ID。因此,我们应用相关标识符;一旦回复者确定该消息无效,它将将该消息的主ID复制到其相关ID以保留该消息的原始ID的记录。处理此无效消息的代码位于Replier类(如前面所示)的onMessage方法中。Requestor.receiveSync()包含类似的无效消息处理代码。
One insight worth noting is that when the message is moved to the invalid message queue, it is actually being resent, so it gets a new message ID. Because of this, we apply Correlation Identifier; once the replier determines the message to be invalid, it copies the message's main ID to its correlation ID to preserve a record of the message's original ID. The code that handles this invalid-message processing is in the Replier class, shown earlier, in the onMessage method. Requestor.receiveSync() contains similar invalid-message processing code.
我们已经了解了如何实现两个类:Requestor和Replier(消息端点),它们使用Request - Reply 交换请求和回复消息。请求消息使用返回地址来指定将回复发送到哪个队列。回复消息使用相关标识符来指定这是对哪个请求的回复。请求者实现轮询消费者来接收回复,而回复者实现事件驱动消费者来接收请求。请求和回复队列是数据类型通道; 当消费者收到类型不正确的消息时,它会将消息重新路由到无效消息通道。
We've seen how to implement two classes, Requestor and Replier (Message Endpoints), that exchange request and reply Messages using Request-Reply. The request message uses a Return Address to specify what queue to send the reply on. The reply message uses a Correlation Identifier to specify which request this is a reply for. The requestor implements a Polling Consumer to receive replies, whereas the replier implements an Event-Driven Consumer to receive requests. The request and reply queues are Datatype Channels; when a consumer receives a message that is not of the right type, it reroutes the message to the Invalid Message Channel.
这是如何使用消息传递的简单示例,以 .NET [ SysMsg ] 和 C# 实现。它展示了如何实现Request-Reply,其中请求者应用程序发送请求,回复者应用程序接收请求并返回回复,然后请求者接收回复。它还显示了如何将无效消息重新路由到特殊通道。
This is a simple example of how to use messaging, implemented in .NET [SysMsg] and C#. It shows how to implement Request-Reply, where a requestor application sends a request, a replier application receives the request and returns a reply, and the requestor receives the reply. It also shows how an invalid message will be rerouted to a special channel.
请求-答复示例的组成部分
Components of the Request-Reply Example
此示例是使用 Microsoft .NET Framework SDK 开发的,并在安装了 MSMQ [ MSMQ ]的 Windows XP 计算机上运行。
This example was developed using the Microsoft .NET Framework SDK and run on a Windows XP computer with MSMQ [MSMQ] installed.
该示例由两个主要类组成。
This example consists of two main classes.
请求者发送请求消息并等待接收回复消息作为响应的 消息。
Requestor A Message Endpoint that sends a request message and waits to receive a reply message as a response.
Replier等待接收请求消息的消息端点 ;当它出现时,它会通过发送回复消息进行响应。
Replier A Message Endpoint that waits to receive the request message; when it does, it responds by sending the reply message.
请求者和回复者将各自作为单独的 .NET 程序运行,这就是分布式通信的原因。
The requestor and the replier will each run as a separate .NET program, which is what makes the communication distributed.
此示例假设消息传递系统定义了以下三个队列:
This example assumes that the messaging system has these three queues defined:
.\private$\ RequestQueue请求者用于将请求消息发送到回复者的MessageQueue 。
.\private$\RequestQueue The MessageQueue the requestor uses to send the request message to the replier.
.\private$\ReplyQueue 回复者用来向请求者发送回复消息的MessageQueue 。
.\private$\ReplyQueue The MessageQueue the replier uses to send the reply message to the requestor.
.\private$\InvalidQueue请求者和回复者收到无法解释的消息时将消息移至的 MessageQueue 。
.\private$\InvalidQueue The MessageQueue to which the requestor and the replier move a message when they receive a message they cannot interpret.
该示例的工作原理如下。当请求程序在命令行窗口中启动时,它会启动并打印如下输出:
Here's how the example works. When the requestor is started in a command-line window, it starts and prints output like this:
发送请求
时间:09:11:09.165342
消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\149
科雷尔。ID:
回复:.\private$\ReplyQueue
内容:世界你好。
Sent request
Time: 09:11:09.165342
Message ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\149
Correl. ID:
Reply to: .\private$\ReplyQueue
Contents: Hello world.
这表明请求者已经发送了请求消息。请注意,即使应答器甚至没有运行并因此无法接收请求,这仍然有效。
This shows that the requestor has sent a request message. Notice that this works even though the replier isn't even running and therefore cannot receive the request.
当回复器在另一个命令行窗口中启动时,它会启动并打印如下输出:
When the replier is started in another command-line window, it starts and prints output like this:
收到请求
时间:09:11:09.375644
消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\149
科雷尔。ID:<不适用>
回复:FORMATNAME:DIRECT=OS:XYZ123\private$\ReplyQueue
内容:世界你好。
已发送回复
时间:09:11:09.956480
消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\150
科雷尔。编号:8b0fc389-f21f-423b-9eaa-c3a881a34808\149
回复:<n/a>
内容:世界你好。
Received request
Time: 09:11:09.375644
Message ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\149
Correl. ID: <n/a>
Reply to: FORMATNAME:DIRECT=OS:XYZ123\private$\ReplyQueue
Contents: Hello world.
Sent reply
Time: 09:11:09.956480
Message ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\150
Correl. ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\149
Reply to: <n/a>
Contents: Hello world.
这表明回复者收到了请求消息并发送了回复消息。
This shows that the replier received the request message and sent a reply message.
此输出中有几个有趣的项目。首先,注意请求发送和接收的时间戳;请求在发送后被接收(210302 毫秒后)。其次,请注意,这两种情况下的消息 ID 相同,因为它是同一条消息。第三,请注意,Hello world 的内容是相同的,这是有道理的,因为这是发送的数据,并且双方必须相同。第四,请求消息指定作为回复消息的目的地的队列(返回地址的示例)。
There are several interesting items in this output. First, notice the request sent and received timestamps; the request was received after it was sent (210302 ms later). Second, notice that the message ID is the same in both cases, because it's the same message. Third, notice that the contents, Hello world, are the same, which makes sense because that's the data that was sent, and it must be the same on both sides. Fourth, the request message specifies the queue that is the destination for the reply message (an example of Return Address).
接下来,我们将接收请求的输出与发送回复的输出进行比较。首先,请注意,直到收到请求后(580836 毫秒后)才发送回复。其次,回复的消息ID与请求的消息ID不同;这是因为请求消息和回复消息是不同的、单独的消息。第三,请求的内容已被提取并添加到回复中。第四,回复目的地未指定,因为不需要回复(回复不使用Return Address )。第五,回复的相关 ID 与请求的消息 ID 相同(回复确实使用Correlation Identifier ) 。
Next, let's compare the output from receiving the request to that for sending the reply. First, notice the reply was not sent until after the request was received (580836 ms after). Second, the message ID for the reply is different from that for the request; this is because the request and reply messages are different, separate messages. Third, the contents of the request have been extracted and added to the reply. Fourth, the reply-to destination is unspecified because no reply is expected (the reply does not use Return Address). Fifth, the reply's correlation ID is the same as the request's message ID (the reply does use Correlation Identifier).
最后,回到第一个窗口,请求者收到以下回复:
Finally, back in the first window, the requestor received the following reply:
收到回复
时间:09:11:10.156467
消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\150
科雷尔。编号:8b0fc389-f21f-423b-9eaa-c3a881a34808\149
回复:<n/a>
内容:世界你好。
Received reply
Time: 09:11:10.156467
Message ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\150
Correl. ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\149
Reply to: <n/a>
Contents: Hello world.
此输出包含几个感兴趣的项目。发送后大约两秒就收到了回复。回复的消息ID在接收时和发送时是相同的,这证明确实是同一条消息。接收到的消息内容与发送的消息内容相同,相关ID告诉请求者这个回复是针对哪个请求的( Correlation Identifier) 。
This output contains several items of interest. The reply was received about two seconds after it was sent. The message ID of the reply was the same when it was received as when it was sent, which proves that it is indeed the same message. The message contents received are the same as those sent, and the correlation ID tells the requestor which request this reply is for (Correlation Identifier).
请求者不会运行很长时间;它发送请求,接收回复,然后退出。但是,回复程序会连续运行,等待请求并发送回复。要停止应答器,我们进入其命令 shell 窗口并按回车键,这会导致应答器程序退出。
The requestor doesn't run for very long; it sends a request, receives a reply, and exits. However, the replier runs continuously, waiting for requests and sending replies. To stop the replier, we go to its command shell window and press the return key, which causes the replier program to exit.
这就是 .NET 请求-答复示例。请求者已准备并发送请求。回复者收到请求并发送回复。然后,请求者收到了对其原始请求的答复。
So, that's the .NET request-reply example. A request was prepared and sent by the requestor. The replier received the request and sent a reply. Then, the requestor received the reply to its original request.
首先我们看一下请求者是如何实现的。
First, let's take a look at how the requestor is implemented.
使用系统;
使用系统消息传递;
公开课请求者
{
私有消息队列请求队列;
私有消息队列回复队列;
公共请求者(字符串请求队列名称,字符串回复队列名称)
{
requestQueue = new MessageQueue(requestQueueName);
回复队列 = new MessageQueue(回复队列名称);
replyQueue.MessageReadPropertyFilter.SetAll();
((XmlMessageFormatter)replyQueue.Formatter).TargetTypeNames =
新字符串[] {“System.String,mscorlib”};
}
公共无效发送()
{
消息 requestMessage = new Message();
requestMessage.Body = "你好世界。";
requestMessage.ResponseQueue = 回复队列;
requestQueue.Send(requestMessage);
Console.WriteLine("已发送请求");
Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
Console.WriteLine("\t消息 ID: {0}", requestMessage.Id);
Console.WriteLine("\tCorrel.ID: {0}", requestMessage.CorrelationId);
Console.WriteLine("\t回复:{0}", requestMessage.ResponseQueue.Path);
Console.WriteLine("\tContents: {0}", requestMessage.Body.ToString());
}
公共无效接收同步()
{
消息回复消息=replyQueue.Receive();
Console.WriteLine("收到回复");
Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
Console.WriteLine("\t消息 ID: {0}",replyMessage.Id);
Console.WriteLine("\tCorrel.ID: {0}",replyMessage.CorrelationId);
Console.WriteLine("\t回复: {0}", "<n/a>");
Console.WriteLine("\tContents: {0}",replyMessage.Body.ToString());
}
}
using System;
using System.Messaging;
public class Requestor
{
private MessageQueue requestQueue;
private MessageQueue replyQueue;
public Requestor(String requestQueueName, String replyQueueName)
{
requestQueue = new MessageQueue(requestQueueName);
replyQueue = new MessageQueue(replyQueueName);
replyQueue.MessageReadPropertyFilter.SetAll();
((XmlMessageFormatter)replyQueue.Formatter).TargetTypeNames =
new string[]{"System.String,mscorlib"};
}
public void Send()
{
Message requestMessage = new Message();
requestMessage.Body = "Hello world.";
requestMessage.ResponseQueue = replyQueue;
requestQueue.Send(requestMessage);
Console.WriteLine("Sent request");
Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
Console.WriteLine("\tMessage ID: {0}", requestMessage.Id);
Console.WriteLine("\tCorrel. ID: {0}", requestMessage.CorrelationId);
Console.WriteLine("\tReply to: {0}", requestMessage.ResponseQueue.Path);
Console.WriteLine("\tContents: {0}", requestMessage.Body.ToString());
}
public void ReceiveSync()
{
Message replyMessage = replyQueue.Receive();
Console.WriteLine("Received reply");
Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
Console.WriteLine("\tMessage ID: {0}", replyMessage.Id);
Console.WriteLine("\tCorrel. ID: {0}", replyMessage.CorrelationId);
Console.WriteLine("\tReply to: {0}", "<n/a>");
Console.WriteLine("\tContents: {0}", replyMessage.Body.ToString());
}
}
想要发送请求和接收回复的应用程序可以使用请求者来执行此操作。应用程序指定两个队列的路径名:请求队列和应答队列。这是请求者初始化自身所需的信息。
An application that wants to send requests and receive replies could use a requestor to do so. The application specifies the pathnames of two queues: the request queue and the reply queue. This is the information the requestor needs to initialize itself.
在请求者构造函数中,请求者使用队列名称连接到消息传递系统。
In the requestor constructor, the requestor uses the queue names to connect to the messaging system.
它使用队列名称来查找队列,即MessageQueue。这些名称是 MSMQ 资源的路径名。
It uses the queue names to look up the queues, which are MessageQueues. The names are pathnames to MSMQ resources.
它设置回复队列的属性过滤器,以便当从队列中读取消息时,该消息的所有属性也将被读取。它还设置格式化程序的TargetTypeNames,以便消息内容将被解释为字符串。
It sets the reply queue's property filter so that when a message is read from the queue, all of the message's properties will be read as well. It also sets the formatter's TargetTypeNames so that the message contents will be interpreted as strings.
请求者必须能够做的一件事是发送请求消息。为此,它实现了Send()方法。
One thing that the requestor must be able to do is send request messages. For that, it implements the Send() method.
它创建一条消息并将其内容设置为“Hello world”。
It creates a message and sets its contents to "Hello world."
它将消息的ResponseQueue属性设置为回复队列。这是一个返回地址,它将告诉回复者如何发回回复。
It sets the message's ResponseQueue property to be the reply queue. This is a Return Address that will tell the replier how to send back the reply.
然后它将消息发送到队列。
It then sends the message to the queue.
然后它打印出刚刚发送的消息的详细信息。这是在消息发送后完成的,因为消息 ID 由消息传递系统设置,直到消息实际发送后才设置。
It then prints out the details of the message it just sent. This is done after the message is sent because the message ID is set by the messaging system and is not set until the message is actually sent.
请求者必须能够做的另一件事是接收回复消息。为此,它实现了ReceiveSync()方法。
The other thing the requestor must be able to do is receive reply messages. It implements the ReceiveSync() method for this purpose.
它运行队列的Receive()方法来获取消息,该方法会同步阻塞,直到消息被传递到队列并从队列中读取,因此请求者是一个Polling Consumer 。由于此接收是同步的,因此请求者的方法称为ReceiveSync()。
It runs the queue's Receive() method to get the message, which synchronously blocks until a message is delivered to the queue and is read from the queue, so the requestor is a Polling Consumer. Because this receive is synchronous, the requestor's method is called ReceiveSync().
请求者获取消息的内容并打印出消息的详细信息。
The requestor gets the message's contents and prints out the message's details.
通过这种方式,请求者可以执行发送请求和接收回复所需的一切操作。
In this way, a requestor does everything necessary to send a request and receive a reply.
接下来我们看看replier是如何实现的。
Next, let's take a look at how the replier is implemented.
使用系统;
使用系统消息传递;
回复者类{
私有消息队列无效队列;
公共回复者(字符串请求队列名称,字符串无效队列名称)
{
MessageQueue requestQueue = new MessageQueue(requestQueueName);
invalidQueue = new MessageQueue(invalidQueueName);
requestQueue.MessageReadPropertyFilter.SetAll();
((XmlMessageFormatter)requestQueue.Formatter).TargetTypeNames =
新字符串[] {“System.String,mscorlib”};
requestQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnReceiveCompleted);
requestQueue.BeginReceive();
}
公共无效OnReceiveCompleted(对象源,ReceiveCompletedEventArgs asyncResult)
{
消息队列请求队列 = (消息队列)源;
消息 requestMessage = requestQueue.EndReceive(asyncResult.AsyncResult);
尝试
{
Console.WriteLine("收到请求");
Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
Console.WriteLine("\t消息 ID: {0}", requestMessage.Id);
Console.WriteLine("\tCorrel.ID: {0}", "<n/a>");
Console.WriteLine("\t回复:{0}", requestMessage.ResponseQueue.Path);
Console.WriteLine("\tContents: {0}", requestMessage.Body.ToString());
字符串内容 = requestMessage.Body.ToString();
MessageQueue 回复队列 = requestMessage.ResponseQueue;
消息回复Message = new Message();
回复消息.Body = 内容;
回复消息.CorrelationId = 请求消息.Id;
回复队列.Send(replyMessage);
Console.WriteLine("已发送回复");
Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
Console.WriteLine("\t消息 ID: {0}",replyMessage.Id);
Console.WriteLine("\tCorrel.ID: {0}",replyMessage.CorrelationId);
Console.WriteLine("\t回复: {0}", "<n/a>");
Console.WriteLine("\tContents: {0}",replyMessage.Body.ToString());
}
捕获(异常){
Console.WriteLine("检测到无效消息");
Console.WriteLine("\tType: {0}", requestMessage.BodyType);
Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
Console.WriteLine("\t消息 ID: {0}", requestMessage.Id);
Console.WriteLine("\tCorrel.ID: {0}", "<n/a>");
Console.WriteLine("\t回复: {0}", "<n/a>");
requestMessage.CorrelationId = requestMessage.Id;
invalidQueue.Send(requestMessage);
Console.WriteLine("发送到无效消息队列");
Console.WriteLine("\tType: {0}", requestMessage.BodyType);
Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
Console.WriteLine("\t消息 ID: {0}", requestMessage.Id);
Console.WriteLine("\tCorrel.ID: {0}", requestMessage.CorrelationId);
Console.WriteLine("\t回复:{0}", requestMessage.ResponseQueue.Path);
}
requestQueue.BeginReceive();
}
}
using System;
using System.Messaging;
class Replier {
private MessageQueue invalidQueue;
public Replier(String requestQueueName, String invalidQueueName)
{
MessageQueue requestQueue = new MessageQueue(requestQueueName);
invalidQueue = new MessageQueue(invalidQueueName);
requestQueue.MessageReadPropertyFilter.SetAll();
((XmlMessageFormatter)requestQueue.Formatter).TargetTypeNames =
new string[]{"System.String,mscorlib"};
requestQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnReceiveCompleted);
requestQueue.BeginReceive();
}
public void OnReceiveCompleted(Object source, ReceiveCompletedEventArgs asyncResult)
{
MessageQueue requestQueue = (MessageQueue)source;
Message requestMessage = requestQueue.EndReceive(asyncResult.AsyncResult);
try
{
Console.WriteLine("Received request");
Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
Console.WriteLine("\tMessage ID: {0}", requestMessage.Id);
Console.WriteLine("\tCorrel. ID: {0}", "<n/a>");
Console.WriteLine("\tReply to: {0}", requestMessage.ResponseQueue.Path);
Console.WriteLine("\tContents: {0}", requestMessage.Body.ToString());
string contents = requestMessage.Body.ToString();
MessageQueue replyQueue = requestMessage.ResponseQueue;
Message replyMessage = new Message();
replyMessage.Body = contents;
replyMessage.CorrelationId = requestMessage.Id;
replyQueue.Send(replyMessage);
Console.WriteLine("Sent reply");
Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
Console.WriteLine("\tMessage ID: {0}", replyMessage.Id);
Console.WriteLine("\tCorrel. ID: {0}", replyMessage.CorrelationId);
Console.WriteLine("\tReply to: {0}", "<n/a>");
Console.WriteLine("\tContents: {0}", replyMessage.Body.ToString());
}
catch ( Exception ) {
Console.WriteLine("Invalid message detected");
Console.WriteLine("\tType: {0}", requestMessage.BodyType);
Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
Console.WriteLine("\tMessage ID: {0}", requestMessage.Id);
Console.WriteLine("\tCorrel. ID: {0}", "<n/a>");
Console.WriteLine("\tReply to: {0}", "<n/a>");
requestMessage.CorrelationId = requestMessage.Id;
invalidQueue.Send(requestMessage);
Console.WriteLine("Sent to invalid message queue");
Console.WriteLine("\tType: {0}", requestMessage.BodyType);
Console.WriteLine("\tTime: {0}", DateTime.Now.ToString("HH:mm:ss.ffffff"));
Console.WriteLine("\tMessage ID: {0}", requestMessage.Id);
Console.WriteLine("\tCorrel. ID: {0}", requestMessage.CorrelationId);
Console.WriteLine("\tReply to: {0}", requestMessage.ResponseQueue.Path);
}
requestQueue.BeginReceive();
}
}
当应用程序需要接收请求并发送回复时,它将实现类似此回复程序的功能。应用程序指定请求和无效消息队列的路径名。(它不需要指定回复队列的名称,因为正如我们稍后将看到的,该名称将由消息的Return Address 提供。 )这是请求者初始化自身所需的信息。
When an application needs to receive a request and send a reply, it will implement something like this replier. The application specifies the pathnames of the request and invalid message queues. (It does not need to specify the name of the reply queue because, as we'll see later, that will be provided by the message's Return Address.) This is the information the requestor needs to initialize itself.
回复者构造函数与请求者的构造函数非常相似,但有一些差异。
The replier constructor is pretty similar to the requestor's, but there are a couple of differences.
一个区别是回复者不查找回复队列。这是因为回复者并不认为它总是会在该队列上发送回复;相反,正如我们将看到的,它会让请求消息告诉它在哪个队列上发送回复消息。
One difference is that the replier does not look up the reply queue. This is because the replier does not assume it will always send replies on that queue; rather, as we'll see, it will let the request message tell it what queue to send the reply message on.
另一个区别是回复者是事件驱动的 Consumer ,因此它设置了ReceiveCompletedEventHandler。当消息传递到请求队列时,消息系统将自动调用指定的方法OnReceiveCompleted 。
Another difference is that the replier is an Event-Driven Consumer, so it sets up a ReceiveCompletedEventHandler. When a message is delivered to the request queue, the messaging system will automatically call the specified method, OnReceiveCompleted.
应答器将自身初始化为请求队列上的侦听器,然后简单地等待消息到达。与必须显式检查回复队列中的消息的请求者不同,回复者是事件驱动的,因此在消息传递系统使用新消息调用其OnReceiveCompleted方法之前不执行任何操作。该消息将来自请求队列,因为构造函数在请求队列上创建了事件处理程序。调用OnReceiveCompleted后,它会执行以下操作来获取新消息并对其进行处理:
The replier initializes itself as a listener on the request queue, and then simply waits for messages to arrive. Unlike the requestor, which has to explicitly check the reply queue for messages, the replier is event-driven and so does nothing until the messaging system calls its OnReceiveCompleted method with a new message. The message will be from the request queue because the constructor created the event handler on the request queue. Once OnReceiveCompleted is called, this is what it does to get the new message and processes it:
源是MessageQueue ,即请求队列。
The source is a MessageQueue, the request queue.
消息本身是通过运行队列的EndReceive方法获取的。然后回复者打印出有关该消息的详细信息。
The message itself is obtained by running the queue's EndReceive method. The replier then prints out the details about the message.
应答器实现其返回地址部分。请记住,请求者设置请求消息的响应队列属性来指定回复队列。回复者现在获取该属性的值并使用它来引用正确的MessageQueue 。这里重要的部分是回复器没有被硬编码为使用特定的回复队列;它使用每个特定请求消息指定的任何回复队列。
The replier implements its part of Return Address. Remember that the requestor set the request message's response-queue property to specify the reply queue. The replier now gets that property's value and uses it to reference the proper MessageQueue. The important part here is that the replier is not hard-coded to use a particular reply queue; it uses whatever reply queue each particular request message specifies.
然后回复者创建回复消息。在此过程中,它通过将回复消息的correlation-id属性设置为与请求消息的message-id属性相同的值来实现相关标识符。
The replier then creates the reply message. In doing so, it implements Correlation Identifier by setting the reply message's correlation-id property to the same value as the request message's message-id property.
然后回复者发出回复消息并显示其详细信息。
The replier then sends out the reply message and displays its details.
如果消息可以接收但未成功处理并抛出异常,则回复者将消息重新发送到无效消息队列。在此过程中,它将新消息的相关 ID 设置为原始消息的消息 ID。
If the message can be received but not successfully processed and an exception is thrown, the replier resends the message to the invalid message queue. In the process, it sets the new message's correlation ID to the original message's message ID.
一旦回复者完成消息处理,它就会运行BeginReceive 以开始侦听下一条消息。
Once the replier has finished processing the message, it runs BeginReceive to start listening for the next message.
因此,回复者会执行接收消息(可能是请求)并发送回复所需的一切操作。如果它无法回复消息,则会将消息路由到无效消息队列。
Thus, a replier does everything necessary to receive a message (presumably a request) and send a reply. If it cannot reply to a message, it routes the message to the invalid message queue.
现在让我们看一个无效消息通道的示例。请记住,我们需要的队列之一是名为private$\InvalidMessages 的队列。其存在是为了如果 MSMQ 客户端(消息端点)收到它无法处理的消息,它可以将奇怪的消息移动到特殊通道。
Let's now look at an example of Invalid Message Channel. Remember, one of the queues we need is the one named private$\InvalidMessages. This exists so that if an MSMQ client (a Message Endpoint) receives a message it cannot process, it can move the strange message to a special channel.
为了演示无效消息处理,我们设计了一个InvalidMessenger 类。该对象专门用于在请求通道上发送格式不正确的消息。与任何通道一样,请求通道是数据类型通道,因为请求接收者期望请求采用某种格式。无效的信使只是以不同的格式发送消息;当回复者收到消息时,它无法识别消息的格式,因此会将消息移至无效消息队列。
To demonstrate invalid message handling, we have designed an InvalidMessenger class. This object is specifically designed to send a message on the request channel whose format is incorrect. Like any channel, the request channel is a Datatype Channel in that the request receivers expect the requests to be in a certain format. The invalid messenger simply sends a message in a different format; when the replier receives the message, it does not recognize the message's format, so it moves the message to the invalid message queue.
我们将在一个窗口中运行回复程序,并在另一个窗口中运行无效的信使。当无效的信使发送消息时,它会显示如下输出:
We'll run the replier in one window and the invalid messenger in another window. When the invalid messenger sends its message, it displays output like this:
发送请求
型号:768
时间:09:39:44.223729
消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\168
科雷尔。ID: 00000000-0000-0000-0000-000000000000\0
回复:.\private$\ReplyQueue
Sent request
Type: 768
Time: 09:39:44.223729
Message ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\168
Correl. ID: 00000000-0000-0000-0000-000000000000\0
Reply to: .\private$\ReplyQueue
类型 768 意味着消息内容的格式是二进制的(而回复者期望内容是文本/XML)。应答者收到无效消息后,将其重新发送到无效消息队列中。
Type 768 means that the format of the message contents is binary (whereas the replier is expecting the contents to be text/XML). The replier receives the invalid message and resends it to the invalid message queue.
检测到无效消息
型号:768
时间:09:39:44.233744
消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\168
科雷尔。ID:<不适用>
回复:<n/a>
发送到无效消息队列
型号:768
时间:09:39:44.233744
消息 ID:8b0fc389-f21f-423b-9eaa-c3a881a34808\169
科雷尔。编号:8b0fc389-f21f-423b-9eaa-c3a881a34808\168
回复:FORMATNAME:DIRECT=OS:XYZ123\private$\ReplyQueue
Invalid message detected
Type: 768
Time: 09:39:44.233744
Message ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\168
Correl. ID: <n/a>
Reply to: <n/a>
Sent to invalid message queue
Type: 768
Time: 09:39:44.233744
Message ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\169
Correl. ID: 8b0fc389-f21f-423b-9eaa-c3a881a34808\168
Reply to: FORMATNAME:DIRECT=OS:XYZ123\private$\ReplyQueue
一个值得注意的见解是,当消息被移动到无效消息队列时,它实际上是在重新发送,因此它会获得一个新的消息 ID。因此,我们应用相关标识符;一旦回复者确定该消息无效,它将将该消息的主ID复制到其相关ID中,以保留该消息的原始ID的记录。处理此无效消息的代码位于Replier类(如前面所示)的OnReceiveCompleted 方法中。
One insight worth noting is that when the message is moved to the invalid message queue, it is actually being resent, so it gets a new message ID. Because of this, we apply Correlation Identifier; once the replier determines the message to be invalid, it copies the message's main ID to its correlation ID so as to preserve a record of the message's original ID. The code that handles this invalid-message processing is in the Replier class, shown earlier, in the OnReceiveCompleted method.
我们已经了解了如何实现两个类:Requestor和Replier(消息端点),它们使用Request - Reply 交换请求并回复消息。请求消息使用返回地址来指定将回复发送到哪个队列。回复消息使用相关标识符来指定这是对哪个请求的回复。请求者实现轮询消费者来接收回复,而回复者实现事件驱动消费者来接收请求。请求和回复队列是数据类型通道; 当消费者收到类型不正确的消息时,它会将消息重新路由到无效消息通道。
We've seen how to implement two classes, Requestor and Replier (Message Endpoints), that exchange a request and reply Messages using Request-Reply. The request message uses a Return Address to specify what queue to send the reply on. The reply message uses a Correlation Identifier to specify which request this is a reply for. The requestor implements a Polling Consumer to receive replies, whereas the replier implements an Event-Driven Consumer to receive requests. The request and reply queues are Datatype Channels; when a consumer receives a message that is not of the right type, it reroutes the message to the Invalid Message Channel.
这是一个简单的示例,展示了发布-订阅消息传递的强大功能并探索了可用的替代设计。它展示了如何通过仅发布一次事件来向多个订阅者应用程序通知单个事件,并考虑如何向订阅者传达该事件的详细信息的替代策略。
This is a simple example that shows the power of publish-subscribe messaging and explores the alternative designs available. It shows how multiple subscriber applications can all be informed of a single event by publishing the event just once and considers alternative strategies for how to communicate details of that event to the subscribers.
使用JMS 主题进行发布-订阅
Publish-Subscribe Using a JMS Topic
要了解简单的发布-订阅通道到底有多大帮助,我们首先需要考虑在多个应用程序之间以分布式方式实现观察者模式是什么样的。在此之前,我们先回顾一下Observer 的基础知识。
To understand how helpful a simple Publish-Subscribe Channel really is, we first need to consider what it is like to implement the Observer pattern in a distributed fashion among multiple applications. Before we get to that, let's review the basics of Observer.
观察者模式[ GoF]记录了一种设计,通过该设计,对象可以将更改通知其依赖者,同时保持该对象与其依赖者解耦,以便该对象无论有多少依赖者都可以正常工作,即使它没有依赖者。全部。它的参与者是一个主题(Subject)——宣布其状态变化的对象;以及观察者(Observers)——对接收主题变化通知感兴趣的对象。当主体的状态发生变化时,它会调用其Notify()方法,该方法的实现知道观察者列表并对每个观察者调用Update() 。一些观察者可能对这种状态变化不感兴趣,但是那些感兴趣的观察者可以通过调用来找出新状态是什么GetState()关于这个主题。主体还必须实现观察者用来注册兴趣的Attach(Observer)和Detach(Observer)方法。
The Observer pattern [GoF] documents a design through which an object can notify its dependents of a change, while keeping that object decoupled from its dependents so that the object works just fine no matter how many dependents it has, even if it has none at all. Its participants are a Subjectthe object announcing changes in its stateand Observersobjects interested in receiving notification of changes in the Subject. When a subject's state changes, it calls its Notify() method, whose implementation knows the list of observers and calls Update() on each of them. Some observers may not be interested in this state change, but those that are can find out what the new state is by calling GetState() on the subject. The subject must also implement Attach(Observer) and Detach(Observer) methods that the observers use to register interest.
观察者提供了两种从主体向观察者获取新状态的方式:推模型和拉模型。使用推送模型,对每个观察者的Update调用都包含新状态作为参数。因此,感兴趣的观察者可以避免调用GetState(),但是将数据传递给不感兴趣的观察者会浪费精力。相反的方法是拉模型,其中主体发送基本通知,每个观察者向主体请求新状态。因此,每个观察者都可以请求它想要的确切细节,甚至根本没有,但主体通常必须满足对同一数据的多个请求。推送模型需要一种单向通信——主体将数据推送给观察者作为更新的一部分。拉模型需要三种单向通信:主体通知观察者,观察者向主体请求当前状态,主体将当前状态发送给观察者。正如我们将看到的,单向通信的数量会影响通知的设计时复杂性和运行时性能。
Observer provides two ways to get the new state from the subject to the observer: the push model and the pull model. With the push model, the Update call to each observer contains the new state as a parameter. Thus, interested observers can avoid having to call GetState(), but effort is wasted passing data to uninterested observers. The opposite approach is the pull model, where the subject sends basic notification and each observer requests the new state from the subject. Thus, each observer can request the exact details it wants, even none at all, but the subject often has to serve multiple requests for the same data. The push model requires a single, one-way communicationthe subject pushes the data to an observer as part of the update. The pull model requires three one-way communicationsthe subject notifies an observer, the observer requests the current state from the subject, and the subject sends the current state to the observer. As we'll see, the number of one-way communications affects both the design-time complexity and the runtime performance of the notification.
实现主题的Notify()方法的最简单方法是使用单个线程,但这可能会产生不良的性能影响。单个线程将按顺序一次更新每个观察者,因此位于一长串观察者末尾的观察者可能需要等待很长时间才能更新。此外,一个花费很长时间更新所有观察者的主题并没有完成任何其他事情。更糟糕的是,观察者很可能使用其更新线程通过查询主题的状态并处理新数据来对更新做出反应;这种观察者在更新线程中工作使得更新过程花费更长的时间。
The easiest way to implement a subject's Notify() method is with a single thread, but that can have undesirable performance implications. A single thread will update each observer one at a time, in sequence, so those at the end of a long list of observers may need to wait a long time for updates. Also, a subject that spends a long time updating all of its observers isn't accomplishing anything else. Even worse, an observer may well use its update thread to react to the update by querying the subject for state and processing the new data; such observer work in the update thread makes the update process take even longer.
因此,实现主题的Notify()方法的更复杂的方法是在其自己的线程中运行每个Update()调用。然后,所有观察者都可以同时更新,并且每个观察者在其更新线程中所做的任何工作都不会延迟其他观察者或主题。缺点是实现多线程和处理线程管理问题更加复杂。
Thus, the more sophisticated way to implement a subject's Notify() method is to run each Update() call in its own thread. Then, all observers can be updated concurrently, and whatever work each may do in its update thread does not delay the other observers or the subject. The downside is that implementing multithreading and handling thread-management issues is more complex.
观察者模式倾向于假设主体及其观察者都在同一个应用程序中运行。该模式的设计支持分布,其中观察者在与主体分离的内存空间中运行,也可能与彼此分离,但分布需要工作。Update ()和GetState()方法以及Attach和Detach方法必须可远程访问(请参阅远程过程调用)。因为主体必须能够调用每个观察者,反之亦然,所以每个对象必须运行在某种类型的对象请求代理 (ORB) 环境中,该环境允许远程调用它所包含的对象。由于更新详细信息和状态数据将在内存空间之间传递,因此应用程序必须能够序列化,即编组它们传递的对象。
The Observer pattern tends to assume that the subject and its observers all run in the same application. The pattern's design supports distribution, where the observers run in a separate memory space from the subject and perhaps from each other, but the distribution takes work. The Update() and GetState() methods, as well as the Attach and Detach methods, must be made remotely accessible (see Remote Procedure Invocation). Because the subject must be able to call each observer, and vice versa, each object must be running in some type of object request broker (ORB) environment that allows the objects it contains to be invoked remotely. Because the update details and state data will be passed between memory spaces, the applications must be able to serialize, that is, marshal the objects they are passing.
因此,在分布式环境中实现观察者可能会变得相当复杂。多线程观察者不仅在实现上有些困难,而且使方法可以远程访问并远程调用它们也增加了更多的难度。仅仅通知一些依赖者状态变化就可能需要大量工作。
Thus, implementing Observer in a distributed environment can get rather complex. Not only is a multithreaded Observer somewhat difficult to implement, but making methods remotely accessibleand invoking them remotelyadds more difficulty. It can be a lot of work just to notify some dependents of state changes.
另一个问题是,远程过程调用仅在调用源、目标以及连接它们的网络都正常工作时才起作用。如果主体宣布更改,而远程观察者未准备好处理通知或与网络断开连接,则观察者将丢失通知。虽然在某些情况下观察者可能在没有通知的情况下正常工作,但在其他情况下,丢失的通知可能会导致观察者与主题不同步,这正是观察者模式旨在防止的问题。
Another problem is that a Remote Procedure Invocation only works when the source of the call, the target, and the network connecting them are all working properly. If a subject announces a change and a remote observer is not ready to process the notification or is disconnected from the network, the observer loses the notification. While the observer may work fine without the notification in some cases, in other cases the lost notification may cause the observer to get out of sync with the subjectthe very problem the Observer pattern is designed to prevent.
与拉动模型相比,分发也更青睐推模型。如前所述,推送需要一种单向通信,而拉动则需要三种通信。当通过 RPC(远程过程调用)实现分发时,推送需要一次调用(Update()),而拉取则需要至少两次调用(Update()和GetState())。RPC 比非分布式方法调用有更多的开销,因此推送方法所需的额外调用会很快损害性能。
Distribution also favors the push model over the pull model. As discussed earlier, push requires a single, one-way communication, whereas pull requires three. When the distribution is implemented via RPCs (Remote Procedure Calls), push requires one call (Update()), whereas pull requires at least two calls (Update() and GetState()). RPCs have more overhead than nondistributed method invocations, so the extra calls required by the push approach can quickly hurt performance.
发布-订阅通道实现了观察者模式,使该模式更易于在分布式应用程序中使用。该模式分三步实施。
A Publish-Subscribe Channel implements the Observer pattern, making the pattern much easier to use among distributed applications. The pattern is implemented in three steps.
消息系统管理员创建一个发布-订阅通道。(这将在 Java 应用程序中表示为 JMS主题。)
The messaging system administrator creates a Publish-Subscribe Channel. (This will be represented in Java applications as a JMS Topic.)
充当主题的应用程序创建一个TopicPublisher (一种MessageProducer )来在通道上发送消息。
The application acting as the subject creates a TopicPublisher (a type of MessageProducer) to send messages on the channel.
每个充当观察者(例如,依赖者)的应用程序都会创建一个TopicSubscriber (一种MessageConsumer )来接收通道上的消息。(这类似于在观察者模式中调用Attach(Observer)方法。)
Each of the applications acting as an observer (e.g., a dependent) creates a TopicSubscriber (a type of MessageConsumer) to receive messages on the channel. (This is analogous to calling the Attach(Observer) method in the Observer pattern.)
这就通过通道在主体和观察者之间建立了联系。现在,每当主题有变化要宣布时,它都会通过发送消息来实现。该通道将确保每个观察者都会收到该消息的副本。
This establishes a connection between the subject and the observers through the channel. Now, whenever the subject has a change to announce, it does so by sending a message. The channel will ensure that each of the observers receives a copy of this message.
以下是宣布更改所需的代码的简单示例:
Here is a simple example of the code needed to announce the change:
导入 javax.jms.Connection;
导入 javax.jms.ConnectionFactory;
导入 javax.jms.Destination;
导入 javax.jms.JMSException;
导入 javax.jms.MessageProducer;
导入javax.jms.Session;
导入 javax.jms.TextMessage;
导入 javax.naming.NamingException;
公共类SubjectGateway {
公共静态最终字符串 UPDATE_TOPIC_NAME = "jms/Update";
private Connection 连接;
私人会议;
私有 MessageProducer updateProducer;
受保护的主题网关() {
极好的();
}
公共静态SubjectGateway newGateway()抛出JMSException,NamingException {
主题网关网关 = new 主题网关();
gateway.initialize();
返回网关;
}
protected voidinitialize() 抛出 JMSException、NamingException {
ConnectionFactory 连接工厂 = JndiUtil.getQueueConnectionFactory();
连接 = connectionFactory.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
目标 updateTopic = JndiUtil.getDestination(UPDATE_TOPIC_NAME);
updateProducer = session.createProducer(updateTopic);
连接.start();
}
公共无效通知(字符串状态)抛出JMSException {
TextMessage 消息 = session.createTextMessage(state);
updateProducer.send(消息);
}
公共无效释放()抛出JMSException {
如果(连接!=空){
连接.stop();
连接.close();
}
}
}
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.NamingException;
public class SubjectGateway {
public static final String UPDATE_TOPIC_NAME = "jms/Update";
private Connection connection;
private Session session;
private MessageProducer updateProducer;
protected SubjectGateway() {
super();
}
public static SubjectGateway newGateway() throws JMSException, NamingException {
SubjectGateway gateway = new SubjectGateway();
gateway.initialize();
return gateway;
}
protected void initialize() throws JMSException, NamingException {
ConnectionFactory connectionFactory = JndiUtil.getQueueConnectionFactory();
connection = connectionFactory.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination updateTopic = JndiUtil.getDestination(UPDATE_TOPIC_NAME);
updateProducer = session.createProducer(updateTopic);
connection.start();
}
public void notify(String state) throws JMSException {
TextMessage message = session.createTextMessage(state);
updateProducer.send(message);
}
public void release() throws JMSException {
if (connection != null) {
connection.stop();
connection.close();
}
}
}
subjectGateway是主题(未示出)和消息传递系统之间的消息传递网关。主体创建网关,然后使用它来广播通知。本质上,主体的Notify()方法被实现为调用SubjectGateway.notify(String)。然后,网关通过在更新通道上发送消息来宣布更改。
SubjectGateway is a Messaging Gateway between the subject (not shown) and the messaging system. The subject creates the gateway and then uses it to broadcast notifications. Essentially, the subject's Notify() method is implemented to call SubjectGateway.notify(String). The gateway then announces the change by sending a message on the update channel.
以下是接收更改通知所需的代码示例:
Here is an example of the code needed to receive the change notification:
导入 javax.jms.Connection;
导入 javax.jms.ConnectionFactory;
导入 javax.jms.Destination;
导入 javax.jms.JMSException;
导入javax.jms.Message;
导入 javax.jms.MessageConsumer;
导入 javax.jms.MessageListener;
导入javax.jms.Session;
导入 javax.jms.TextMessage;
导入 javax.naming.NamingException;
公共类 ObserverGateway 实现 MessageListener {
公共静态最终字符串 UPDATE_TOPIC_NAME = "jms/Update";
私人观察员观察员;
private Connection 连接;
私人消息消费者更新消费者;
受保护的 ObserverGateway() {
极好的();
}
公共静态ObserverGateway newGateway(观察者观察者)
抛出 JMSException、NamingException {
ObserverGateway 网关 = new ObserverGateway();
gateway.initialize(观察者);
返回网关;
}
protected voidinitialize(观察者观察者)抛出JMSException,NamingException {
this.observer = 观察者;
ConnectionFactory 连接工厂 = JndiUtil.getQueueConnectionFactory();
连接 = connectionFactory.createConnection();
会话 session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
目标 updateTopic = JndiUtil.getDestination(UPDATE_TOPIC_NAME);
updateConsumer = session.createConsumer(updateTopic);
updateConsumer.setMessageListener(this);
}
公共无效onMessage(消息消息){
尝试 {
TextMessage textMsg = (TextMessage) 消息;// 假设强制转换始终有效
String newState = textMsg.getText();
更新(新状态);
} catch (JMSException e) {
e.printStackTrace();
}
}
公共无效附加()抛出JMSException {
连接.start();
}
公共无效分离()抛出JMSException {
如果(连接!=空){
连接.stop();
连接.close();
}
}
私有无效更新(字符串newState)抛出JMSException {
观察者.更新(newState);
}
}
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.NamingException;
public class ObserverGateway implements MessageListener {
public static final String UPDATE_TOPIC_NAME = "jms/Update";
private Observer observer;
private Connection connection;
private MessageConsumer updateConsumer;
protected ObserverGateway() {
super();
}
public static ObserverGateway newGateway(Observer observer)
throws JMSException, NamingException {
ObserverGateway gateway = new ObserverGateway();
gateway.initialize(observer);
return gateway;
}
protected void initialize(Observer observer) throws JMSException, NamingException {
this.observer = observer;
ConnectionFactory connectionFactory = JndiUtil.getQueueConnectionFactory();
connection = connectionFactory.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination updateTopic = JndiUtil.getDestination(UPDATE_TOPIC_NAME);
updateConsumer = session.createConsumer(updateTopic);
updateConsumer.setMessageListener(this);
}
public void onMessage(Message message) {
try {
TextMessage textMsg = (TextMessage) message; // assume cast always works
String newState = textMsg.getText();
update(newState);
} catch (JMSException e) {
e.printStackTrace();
}
}
public void attach() throws JMSException {
connection.start();
}
public void detach() throws JMSException {
if (connection != null) {
connection.stop();
connection.close();
}
}
private void update(String newState) throws JMSException {
observer.update(newState);
}
}
ObserverGateway 是另一个消息传递网关,这次位于观察者(未显示)和消息传递系统之间。观察者创建网关,然后使用Attach()启动连接(类似于在观察者模式中调用Attach(Observer)方法)。网关是一个事件驱动的消费者,因此它实现了MessageListener接口,该接口需要onMessage方法。这样,当收到更新时,网关会处理消息以获取新状态并调用自己的update(String)方法,然后调用观察者中相应的消息。
ObserverGateway is another Messaging Gateway, this time between the observer (not shown) and the messaging system. The observer creates the gateway, then uses attach() to start the connection (which is analogous to calling the Attach(Observer) method in the Observer pattern). The gateway is an Event-Driven Consumer, so it implements the MessageListener interface, which requires the onMessage method. In this way, when an update is received, the gateway processes the message to get the new state and calls its own update(String) method, which then calls the corresponding message in the observer.
这两个类实现了 Observer 的推送模型版本。 通过SubjectGateway.notify(String)发送的通知消息,消息的存在告诉观察者发生了变化,但消息的内容告诉观察者主题的新状态是什么。新的状态被从主体推向观察者。正如我们稍后将看到的,还有另一种使用拉模型来实现此功能的方法。
These two classes implement the push model version of Observer. With the notification message sent by SubjectGateway.notify(String), the existence of the message tells the observer that a change has occurred, but it is the contents of the message that tell the observer what the subject's new state is. The new state is being pushed from the subject to the observer. As we'll see later, there's another way to implement this functionality using the pull model.
对于应用程序之间的分布式通知,发布-订阅(例如消息传递)方法比实现Observer 的传统同步(例如 RPC)方法具有多个优点。
For distributed notification between applications, the publish-subscribe (e.g., messaging) approach has several advantages over the traditional, synchronous (e.g., RPC) approach of implementing Observer.
简化通知。主题的Notify()实现变得异常简单;代码只需在通道上发送消息即可。同样,Observer.Update()只需接收一条消息。
Simplifies notification. The subject's implementation of Notify() becomes incredibly simple; the code just has to send a message on a channel. Likewise, Observer.Update() just has to receive a message.
简化附加/分离。观察者需要订阅和取消订阅频道,而不是附加到主题或从主题分离。主体不需要实现Attach(Observer)或Detach(Observer)(尽管观察者可以实现这些方法来封装订阅和取消订阅行为)。
Simplifies attach/detach. Rather than attach to and detach from the subject, an observer needs to subscribe to and unsubscribe from the channel. The subject does not need to implement Attach(Observer) or Detach(Observer) (although the observer may implement these methods to encapsulate the subscribe and unsubscribe behavior).
简化并发线程。主体只需要一个线程来同时更新所有观察者,通道同时向观察者传递通知消息,每个观察者在自己的线程中处理更新。这简化了主体的实现,并且因为每个观察者都使用自己的线程,所以一个观察者在其更新线程中所做的事情不会影响其他观察者。
Simplifies concurrent threading. The subject needs only one thread to update all observers concurrentlythe channel delivers the notification message to the observers concurrentlyand each observer handles the update in its own thread. This simplifies the subject's implementation, and because each observer uses its own thread, what one does in its update thread does not affect the others.
简化远程访问。主体和观察者都不需要实现任何远程方法,也不需要在 ORB 中运行。他们只需要访问消息传递系统,它就会处理分发。
Simplifies remote access. Neither the subject nor the observers have to implement any remote methods, nor do they need to run in an ORB. They just need to access the messaging system, and it handles the distribution.
提高可靠性。由于通道使用消息传递,通知将排队直到观察者可以处理它们,这也使观察者能够限制通知。如果观察者想要接收在观察者断开连接时发送的通知,它应该使自己成为持久订阅者。
Increases reliability. Because the channel uses messaging, notifications will be queued until the observer can process them, which also enables the observer to throttle the notifications. If an observer wants to receive notifications that are sent while that observer is disconnected, it should make itself a Durable Subscriber.
发布-订阅方法没有改变的一个问题是序列化。无论观察者是通过RPC还是消息传递实现的,状态数据都是从主体的内存空间分发到每个观察者的内存空间,因此数据必须被序列化(即编组)。无论哪种方法都必须实现此行为。
One issue that the publish-subscribe approach does not change is serialization. Whether Observer is implemented through RPC or messaging, state data is being distributed from the subject's memory space to each observer's memory space, so the data has to be serialized (i.e., marshaled). This behavior has to be implemented for either approach.
如果说发布-订阅方法有一个缺点,那就是该方法需要消息传递,这意味着主题和观察者应用程序必须能够访问共享消息传递系统,并且必须作为该消息传递系统的客户端来实现。尽管如此,将应用程序变成消息传递客户端并不比使用 RPC 方法更困难,甚至可能更容易。
If the publish-subscribe approach has a downside, it's that the approach requires messaging, which means that the subject and observer applications must have access to a shared messaging system and must be implemented as clients of that messaging system. Still, making applications into messaging clients is no more difficult, and probably easier, than using the RPC approach.
发布-订阅方法的另一个潜在缺点是拉模型比推模型更复杂。如前所述,拉模型比推模型需要更多的来回讨论。当讨论是在分布式应用程序之间进行时,额外的通信会严重损害性能。
Another potential downside of the publish-subscribe approach is that the pull model is more complex than the push model. As discussed earlier, the pull model requires more back-and-forth discussion than the push model. When the discussion is among distributed applications, the extra communication can significantly hurt performance.
消息传递的通信比 RPC 更复杂。在这两种情况下,Update()都是一种单向通信,要么是返回 void 的 RPC,要么是从主体到观察者的单个事件消息。更棘手的部分是当观察者需要查询主体的状态时。GetState()是双向通信,可以是请求状态并返回状态的单个 RPC,也可以是请求-应答一对消息,其中命令消息请求状态并单独的文档消息返回状态。
The communication is more complex with messaging than with RPC. In both cases, Update() is a one-way communication, either an RPC that returns void or a single Event Message from the subject to the observer. The trickier part is when an observer needs to query the subject's state. GetState() is a two-way communication, either a single RPC that requests the state and returns it, or a Request-Reply a pair of messages where a Command Message requests the state and a separate Document Message returns it.
是什么构成了请求-回复更困难的不仅是它需要一对消息,而且还需要一对通道来传输这些消息。其中一个通道是“获取状态请求”通道,从观察者到主体;观察者在该通道上发送状态请求。另一个通道,即获取状态回复通道,从主体返回到观察者;主体在该通道上发送状态回复。所有观察者都可以共享相同的请求通道,但他们可能每个都需要自己的回复通道。每个观察者不仅需要接收任何响应,还需要接收其特定请求的特定响应,确保这一点的最简单方法是每个观察者都有自己的回复通道。(另一种方法是使用单个回复通道并使用相关标识符来确定
What makes Request-Reply more difficult is not just that it requires a pair of messages, but that it requires a pair of channels to transmit those messages. One channel, the get-state-request channel, goes from an observer to the subject; an observer sends the state request on that channel. The other channel, the get-state-reply channel, goes from the subject back to the observer; the subject sends the state reply on that channel. All of the observers can share the same request channel, but they will probably each need their own reply channel. Each observer needs to receive not just any response but the particular response for its specific request, and the easiest way to ensure this is for each observer to have its own reply channel. (An alternative is to use a single reply channel and use Correlation Identifiers to figure out which reply goes to which observer, but a separate channel per observer is a lot easier to implement.)
使用拉模型发布-订阅
Publish-Subscribe Using the Pull Model
每个观察者的回复通道可能会导致通道爆炸。如此大量的通道可能是可以管理的,但是当必须使用这些通道的观察者数量在运行时动态变化时,消息传递系统管理员不知道要创建多少个静态通道。即使有足够的通道供所有观察者使用,每个观察者如何知道使用哪个通道?
A reply channel for each observer can lead to an explosion of channels. Such a large number of channels may be manageable, but the messaging system administrator does not know how many static channels to create when the number of observers that must use these channels changes dynamically at runtime. Even if there are enough channels for all of the observers, how does each observer know which channel to use?
JMS 有一个功能TemporaryQueue ,专门用于此目的 [ Hapner ] 。(另请参阅Request-Reply的讨论。)观察者可以创建一个专门供自己使用的临时队列,将该队列指定为其请求中的返回地址,并等待该队列上的回复。频繁创建新队列可能效率低下,具体取决于消息传递系统的实现,并且临时队列无法持久化(与保证交付一起使用) 。但是,如果您不想使用推送模型,则可以使用临时队列来实现拉取模型。
JMS has a feature, TemporaryQueue, specifically for this purpose [Hapner]. (Also see the discussion of Request-Reply.) An observer can create a temporary queue exclusively for its own use, specify that queue as the Return Address in its request, and wait for the reply on that queue. Creating new queues frequently can be inefficient, depending on your messaging system's implementation, and temporary queues cannot be persistent (for use with Guaranteed Delivery). However, if you don't want to use the push model, you can implement the pull model using temporary queues.
这两个类展示了如何使用拉模型实现网关。
These two classes show how to implement the gateways using the pull model.
导入 javax.jms.Connection;
导入 javax.jms.ConnectionFactory;
导入 javax.jms.Destination;
导入 javax.jms.JMSException;
导入javax.jms.Message;
导入 javax.jms.MessageConsumer;
导入 javax.jms.MessageListener;
导入 javax.jms.MessageProducer;
导入javax.jms.Session;
导入 javax.jms.TextMessage;
导入 javax.naming.NamingException;
公共类 PullSubjectGateway {
公共静态最终字符串 UPDATE_TOPIC_NAME = "jms/Update";
私人 PullSubject 主题;
private Connection 连接;
私人会议;
私有 MessageProducer updateProducer;
受保护的 PullSubjectGateway() {
极好的();
}
公共静态 PullSubjectGateway newGateway(PullSubject 主题)
抛出 JMSException、NamingException {
PullSubjectGateway 网关 = new PullSubjectGateway();
gateway.initialize(主题);
返回网关;
}
protected voidinitialize(PullSubject subject) 抛出 JMSException, NamingException {
this.subject = 主题;
ConnectionFactory 连接工厂 = JndiUtil.getQueueConnectionFactory();
连接 = connectionFactory.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
目标 updateTopic = JndiUtil.getDestination(UPDATE_TOPIC_NAME);
updateProducer = session.createProducer(updateTopic);
new Thread(new GetStateReplier()).start();
连接.start();
}
公共无效notifyNoState()抛出JMSException {
TextMessage 消息 = session.createTextMessage();
updateProducer.send(消息);
}
公共无效释放()抛出JMSException {
如果(连接!=空){
连接.stop();
连接.close();
}
}
私有类 GetStateReplier 实现 Runnable、MessageListener {
公共静态最终字符串 GET_STATE_QUEUE_NAME = "jms/GetState";
私人会议;
私有 MessageConsumer 请求消费者;
公共无效运行(){
尝试 {
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
目标 getStateQueue = JndiUtil.getDestination(GET_STATE_QUEUE_NAME);
requestConsumer = session.createConsumer(getStateQueue);
requestConsumer.setMessageListener(this);
} catch (异常 e) {
e.printStackTrace();
}
}
公共无效onMessage(消息消息){
尝试 {
目标回复队列 = message.getJMSReplyTo();
MessageProducer 回复生产者 = session.createProducer(replyQueue);
消息回复Message = session.createTextMessage(subject.getState());
回复Producer.send(replyMessage);
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.NamingException;
public class PullSubjectGateway {
public static final String UPDATE_TOPIC_NAME = "jms/Update";
private PullSubject subject;
private Connection connection;
private Session session;
private MessageProducer updateProducer;
protected PullSubjectGateway() {
super();
}
public static PullSubjectGateway newGateway(PullSubject subject)
throws JMSException, NamingException {
PullSubjectGateway gateway = new PullSubjectGateway();
gateway.initialize(subject);
return gateway;
}
protected void initialize(PullSubject subject) throws JMSException, NamingException {
this.subject = subject;
ConnectionFactory connectionFactory = JndiUtil.getQueueConnectionFactory();
connection = connectionFactory.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination updateTopic = JndiUtil.getDestination(UPDATE_TOPIC_NAME);
updateProducer = session.createProducer(updateTopic);
new Thread(new GetStateReplier()).start();
connection.start();
}
public void notifyNoState() throws JMSException {
TextMessage message = session.createTextMessage();
updateProducer.send(message);
}
public void release() throws JMSException {
if (connection != null) {
connection.stop();
connection.close();
}
}
private class GetStateReplier implements Runnable, MessageListener {
public static final String GET_STATE_QUEUE_NAME = "jms/GetState";
private Session session;
private MessageConsumer requestConsumer;
public void run() {
try {
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Destination getStateQueue = JndiUtil.getDestination(GET_STATE_QUEUE_NAME);
requestConsumer = session.createConsumer(getStateQueue);
requestConsumer.setMessageListener(this);
} catch (Exception e) {
e.printStackTrace();
}
}
public void onMessage(Message message) {
try {
Destination replyQueue = message.getJMSReplyTo();
MessageProducer replyProducer = session.createProducer(replyQueue);
Message replyMessage = session.createTextMessage(subject.getState());
replyProducer.send(replyMessage);
} catch (JMSException e) {
e.printStackTrace();
}
}
}
}
PullSubjectGateway 与SubjectGateway 非常相似。 拉取版本现在具有对其主题的引用,因此网关可以在观察者请求时查询主题的状态。notify(String)现在已变为notifyNoState(),因为拉取模型只是发送通知而不包含任何状态(并且因为 Java 已经使用方法名称notify())。
PullSubjectGateway is very similar to SubjectGateway. The pull version now has a reference to its subject, so the gateway can query the subject for its state when requested by an observer. notify(String) has now become notifyNoState(), because the pull model simply sends out notification without including any state (and because Java already uses the method name notify()).
拉取模型的一个重要补充是GetStateReplier ,它是一个实现Runnable 的,以便它可以在自己的线程中运行。它也是一个MessageListener ,这使其成为一个事件驱动的 Consumer 。它的onMessage方法从GetState 队列读取请求,并将包含主题状态的回复发送到请求指定的队列。这样,当观察者发出GetState()请求时,网关会发送回复(请参阅Request-Reply)。
The big addition for the pull model is GetStateReplier, an inner class that implements Runnable so that it can run in its own thread. It is also a MessageListener, which makes it an Event-Driven Consumer. Its onMessage method reads requests from the GetState queue and sends replies containing the subject's state to the queue specified by the request. In this way, when an observer makes a GetState() request, the gateway sends a reply (see Request-Reply).
导入 javax.jms.Destination;
导入 javax.jms.JMSException;
导入javax.jms.Message;
导入 javax.jms.MessageConsumer;
导入 javax.jms.MessageListener;
导入javax.jms.Queue;
导入 javax.jms.QueueConnection;
导入 javax.jms.QueueConnectionFactory;
导入 javax.jms.QueueRequestor;
导入 javax.jms.QueueSession;
导入javax.jms.Session;
导入 javax.jms.TextMessage;
导入 javax.naming.NamingException;
公共类 PullObserverGateway 实现 MessageListener {
公共静态最终字符串 UPDATE_TOPIC_NAME = "jms/Update";
公共静态最终字符串 GET_STATE_QUEUE_NAME = "jms/GetState";
私有 PullObserver 观察者;
私有 QueueConnection 连接;
私有 QueueSession 会话;
私人消息消费者更新消费者;
私有 QueueRequestor getStateRequestor;
受保护的 PullObserverGateway() {
极好的();
}
公共静态 PullObserverGateway newGateway(PullObserver 观察者)
抛出 JMSException、NamingException {
PullObserverGateway 网关 = new PullObserverGateway();
gateway.initialize(观察者);
返回网关;
}
protected voidinitialize(PullObserver观察者)抛出JMSException,NamingException {
this.observer = 观察者;
QueueConnectionFactory 连接工厂 = JndiUtil.getQueueConnectionFactory();
连接 = connectionFactory.createQueueConnection();
session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
目标 updateTopic = JndiUtil.getDestination(UPDATE_TOPIC_NAME);
updateConsumer = session.createConsumer(updateTopic);
updateConsumer.setMessageListener(this);
队列 getStateQueue = (Queue) JndiUtil.getDestination(GET_STATE_QUEUE_NAME);
getStateRequestor = new QueueRequestor(会话, getStateQueue);
}
公共无效onMessage(消息消息){
尝试 {
// 消息内容为空
updateNoState();
} catch (JMSException e) {
e.printStackTrace();
}
}
公共无效附加()抛出JMSException {
连接.start();
}
公共无效分离()抛出JMSException {
如果(连接!=空){
连接.stop();
连接.close();
}
}
私有无效 updateNoState() 抛出 JMSException {
TextMessage getStateRequestMessage = session.createTextMessage();
消息 getStateReplyMessage = getStateRequestor.request(getStateRequestMessage);
TextMessage textMsg = (TextMessage) getStateReplyMessage; // 假设强制转换始终有效
String newState = textMsg.getText();
观察者.更新(newState);
}
}
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageConsumer;
import javax.jms.MessageListener;
import javax.jms.Queue;
import javax.jms.QueueConnection;
import javax.jms.QueueConnectionFactory;
import javax.jms.QueueRequestor;
import javax.jms.QueueSession;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.naming.NamingException;
public class PullObserverGateway implements MessageListener {
public static final String UPDATE_TOPIC_NAME = "jms/Update";
public static final String GET_STATE_QUEUE_NAME = "jms/GetState";
private PullObserver observer;
private QueueConnection connection;
private QueueSession session;
private MessageConsumer updateConsumer;
private QueueRequestor getStateRequestor;
protected PullObserverGateway() {
super();
}
public static PullObserverGateway newGateway(PullObserver observer)
throws JMSException, NamingException {
PullObserverGateway gateway = new PullObserverGateway();
gateway.initialize(observer);
return gateway;
}
protected void initialize(PullObserver observer) throws JMSException, NamingException {
this.observer = observer;
QueueConnectionFactory connectionFactory = JndiUtil.getQueueConnectionFactory();
connection = connectionFactory.createQueueConnection();
session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
Destination updateTopic = JndiUtil.getDestination(UPDATE_TOPIC_NAME);
updateConsumer = session.createConsumer(updateTopic);
updateConsumer.setMessageListener(this);
Queue getStateQueue = (Queue) JndiUtil.getDestination(GET_STATE_QUEUE_NAME);
getStateRequestor = new QueueRequestor(session, getStateQueue);
}
public void onMessage(Message message) {
try {
// message's contents are empty
updateNoState();
} catch (JMSException e) {
e.printStackTrace();
}
}
public void attach() throws JMSException {
connection.start();
}
public void detach() throws JMSException {
if (connection != null) {
connection.stop();
connection.close();
}
}
private void updateNoState() throws JMSException {
TextMessage getStateRequestMessage = session.createTextMessage();
Message getStateReplyMessage = getStateRequestor.request(getStateRequestMessage);
TextMessage textMsg = (TextMessage) getStateReplyMessage; // assume cast always works
String newState = textMsg.getText();
observer.update(newState);
}
}
同样, PullObserverGateway 与ObserverGateway类似,但多了一些代码来实现拉模型。在初始化时,它不仅设置updateConsumer 来监听更新,还设置 getStateRequestor 来发送GetState ()请求。(getStateRequestor 是QueueRequestor ;请参阅Request - Reply 。)在拉取版本中,网关的onMessage代码忽略消息的内容,因为消息是空的。消息的存在告诉观察者主体已经改变,但它并没有告诉观察者主体的新状态是什么。因此,所需要做的就是调用updateNoState() (名称与notifyNoState()类似)。
Again, PullObserverGateway is similar to ObserverGateway but with some more code to implement the pull model. In initialize, it sets up not only updateConsumer to listen for updates but also getStateRequestor to send GetState() requests. (getStateRequestor is a QueueRequestor; see Request-Reply.) In the pull version, the gateway's onMessage code ignores the message's contents because the message is empty. The message's existence tells the observer that the subject has changed, but it does not tell the observer what the subject's new state is. So, all there is to do is call updateNoState() (named similarly to notifyNoState()).
对于观察者来说,推模型和拉模型之间的差异在updateNoState()与update(String)的实现中变得显而易见。推送版本获取新状态作为参数并且只需更新观察者,而拉取版本必须先获取新状态才能更新观察者。为了获取新状态,它使用getStateRequestor发送请求并获得回复。回复包含主题的新状态,网关用它来更新观察者。(请注意,在这个简单的实现中,网关是单线程的,因此在发送获取状态请求并等待回复时,它不会再处理任何更新。因此,如果请求或回复消息确实需要传输时间过长,网关将陷入等待,并且发生的任何更多更新都将简单地排队。)
The difference for the observer between the push and pull models becomes apparent in the implementation of updateNoState() versus update(String). Whereas the push version gets the new state as a parameter and just has to update the observer, the pull version must go get the new state before it can update the observer. To get the new state, it uses the getStateRequestor to send a request and get the reply. The reply contains the subject's new state, which the gateway uses to update the observer. (Note that in this simple implementation, the gateway is single-threaded, so while it is sending the get-state request and waiting for the reply, it is not processing any more updates. Thus, if the request or reply messages take a really long time to transmit, the gateway will be stuck waiting, and any more updates that occur will simply queue up.)
正如您所看到的,拉模型比推模型更复杂。它需要更多的通道(包括每个观察者的临时通道)和更多的消息(每个感兴趣的观察者每次更新三条消息,而不是所有观察者一条消息),主题和观察者类需要更多代码来管理附加消息传递,而对象在运行时需要更多线程来执行附加消息传递。如果所有这些在您的应用程序中都是可以接受的,那么拉模型就是一种可行的方法。但是,如果有疑问,您可能应该从推送模型开始,因为它更简单。
As you can see, the pull model is more complex than the push model. It requires more channels (including a temporary one for every observer) and more messages (three messages per update per interested observer instead of one message for all observers), the subject and observer classes require more code to manage the additional messaging, and the objects at runtime require more threads to execute the additional messaging. If all of this is acceptable in your application, then the pull model is a viable approach. However, if in doubt, you should probably start with the push model because it is simpler.
到目前为止,我们已经考虑了一个主题,其中一个状态通知其观察者。使用推送模型,这需要一个发布-订阅通道来将主体状态的变化传达给观察者。
So far, we've considered one subject with one piece of state notifying its observers. Using the push model, this requires one Publish-Subscribe Channel for communicating changes in the subject's state to the observers.
真正的企业应用程序要复杂得多。一个应用程序可能包含许多需要宣布更改的主题。每个主题通常包含几个不同的状态部分,称为方面,它们可以独立更改。单个观察者可能对几个不同主题的几个不同方面感兴趣,其中主题不仅是同一类的多个实例,而且很可能是不同类的实例。
Real enterprise applications are much more complex. An application can contain lots of subjects that need to announce changes. Each subject often contains several different pieces of state, called aspects, that can change independently. A single observer might be interested in several different aspects in several different subjects, where the subjects are not only multiple instances of the same class but may well be instances of different classes.
因此,复杂应用程序中的更新语义很快就会变得复杂。观察者模式将这一问题解决为“观察多个主题”和“明确指定感兴趣的修改”等实现问题。此外,SASE(自地址邮票信封)模式描述了观察者模式和命令模式的组合,其中观察者指定当发生某种变化时主体应发送的命令[ Alpert] 。
So, update semantics in sophisticated applications can quickly become complex. The Observer pattern addresses this as implementation issues like "Observing more than one subject" and "Specifying modifications of interest explicitly." Also, the SASE (Self-Addresses Stamped Envelope) pattern describes a combination of the Observer and Command patterns whereby an observer specifies the command a subject should send it when a certain change occurs [Alpert].
在不深入讨论确保观察者只收到他们需要的更新的问题的情况下,让我们考虑一下消息传递的影响,即:我们需要多少个渠道?
Without getting too deep into the issues of making sure observers receive only the updates they need, let's consider the implications for messaging, namely: How many channels will we need?
我们首先考虑一个简单的情况。企业可能有多个不同的应用程序负责存储客户的联系信息,例如邮寄地址。当客户的地址在这些应用程序之一中更新时,该应用程序应通知可能也需要此新信息的其他应用程序。同时,可能有多个应用程序需要知道地址何时发生变化,因此它们希望注册以接收通知。
Let's first consider a simple case. An enterprise may have several different applications responsible for storing a customer's contact information, such as a mailing address. When a customer's address is updated in one of these applications, the application should notify other applications that may need this new information as well. Meanwhile, there may be several applications that need to know when an address changes, so they would like to register to receive notification.
这是一个很容易解决的问题。所需要的只是一个用于宣布地址更改的发布-订阅通道。每个可以更改地址的应用程序也有责任通过在通道上发布消息来宣布该更改。每个希望接收通知的应用程序都会订阅该频道。特定的更改消息可能如下所示。
This is a simple problem to solve. All that is needed is a single Publish-Subscribe Channel for announcing address changes. Each application that can change an address then also has the responsibility to announce that change by publishing a message on the channel. Each application that wishes to receive notification subscribes to the channel. A particular change message might look like this.
<地址更改 customer_id="12345">
<旧地址>
<街>华尔街123号</街>
<城市>纽约</城市>
<州>纽约州</州>
<邮编>10005</邮编>
</旧地址>
<新地址>
<街道>日落大道321号</街道>
<城市>洛杉矶</城市>
<州>加利福尼亚州</州>
<邮编>90012</邮编>
</新地址>
</地址更改>
<AddressChange customer_id="12345">
<OldAddress>
<Street>123 Wall Street</Street>
<City>New York</City>
<State>NY</State>
<Zip>10005</Zip>
</OldAddress>
<NewAddress>
<Street>321 Sunset Blvd</Street>
<City>Los Angeles</City>
<State>CA</State>
<Zip>90012</Zip>
</NewAddress>
</AddressChange>
现在让我们考虑另一个问题。企业还可能有一些应用程序需要在产品缺货时进行通知,而其他应用程序则需要接收这些通知以便重新订购产品。这只是最后一个问题的不同示例,通过使用发布-订阅通道发布产品缺货公告以相同的方式解决。其中一条消息可能如下所示。
Now let's consider another problem. The enterprise may also have applications that need to announce when they are out of a product and others that need to receive these notifications so that can reorder the product. This is just a different example of the last problem, and it is solved the same way by using a Publish-Subscribe Channel to make out-of-product announcements. One of these messages might look like this.
<缺货>
<产品ID>12345</产品ID>
<商店ID>67890</商店ID>
<请求数量>100</请求数量>
</产品外>
<OutOfProduct>
<ProductID>12345</ProductID>
<StoreID>67890</StoreID>
<QuantityRequested>100</QuantityRequested>
</OutOfProduct>
但这让我们想知道:我们可以使用相同的渠道来更改客户地址和发布产品缺货通知吗?可能不会。首先,数据类型通道告诉我们通道上的所有消息必须是相同的类型,在本例中这意味着它们必须都符合相同的 XML 模式。<AddressChange>显然是与<OutOfProduct>非常不同的元素类型,因此它们不应在同一通道上发送。也许可以重新设计数据格式,以便两种消息类型符合相同的模式,然后接收者可以辨别哪些消息是针对地址的,哪些是针对产品的。但问题是,对地址更改感兴趣的应用程序可能与对产品更新感兴趣的应用程序不同,因此,如果消息使用相同的通道,应用程序将经常收到它不感兴趣的通知。因此,有道理两个独立的地址变更和产品更新渠道。
But this leads us to wonder: Can we use the same channel for customer address changes and for out-of-product announcements? Probably not. First, Datatype Channel tells us that all of the messages on a channel must be the same type, which in this case means that they must all conform to the same XML schema. <AddressChange> is obviously a very different element type from <OutOfProduct>, so they should not be sent on the same channel. Perhaps the data formats could be reworked so that both message types fit the same schema, and then receivers could tell which messages were for addresses and which were for products. But then the problem is that the applications interested in address changes are probably not the same ones interested in product updates, so if the messages use the same channel, an application will frequently receive notifications it's not interested in. Thus, it makes sense to have two separate address change and product update channels.
现在,考虑第三种情况,即客户的信用评级可能发生变化。该消息可能如下所示:
Now, consider a third case where a customer's credit rating could change. The message might look like this:
<CreditRatingChange customer_id="12345">
<旧评级>AAA</旧评级>
<新评级>BBB</新评级>
</信用评级变更>
<CreditRatingChange customer_id="12345">
<OldRating>AAA</OldRating>
<NewRating>BBB</NewRating>
</CreditRatingChange>
与产品通知的情况一样,使用新的信用评级更改渠道(除了地址更改和产品外渠道之外)可能很容易解决问题。这将使信用评级的变化与地址的变化分开,并且允许家属只注册他们感兴趣的变化类型。
Like the case with product notifications, it might be tempting to solve the problem with a new credit-rating-changed channel (in addition to the address-changed and out-of-product channels). This would keep the credit rating changes separate from the address changes, and it would allow dependents to only register for the type of changes they're interested in.
这种方法的问题是它可能导致通道爆炸。考虑可能了解的有关客户的所有数据:姓名;用于邮寄、运输和计费的联系人(地址、电话号码、电子邮件);信用评级; 服务水平; 标准折扣;等等。每当这些方面中的任何一个发生变化时,其他应用程序都可能需要了解它。为每个通道创建一个通道可能会产生很多通道。
The problem with this approach is that it can lead to a channel explosion. Consider all the pieces of data that may be known about a customer: name; contacts (address, phone number, e-mail) for mailing, shipping, and billing; credit rating; service level; standard discount; and so on. Each time any one of these aspects changes, other applications may need to know about it. Creating a channel for each can lead to lots of channels.
大量的通道可能会给消息系统带来负担。许多通道上的流量很小,这可能会浪费资源并使负载难以分配。包含大量小消息的众多通道可能会增加消息传递开销。家属可能会对要订阅大量频道中的哪一个感到困惑。多个通道需要多个发送者和接收者,可能会导致大量线程检查大量通常为空的通道。因此,创建更多渠道可能不是一个好主意。
Large numbers of channels may tax the messaging system. Numerous channels with little traffic on each can waste resources and make load difficult to distribute. Numerous channels with lots of little messages can add to messaging overhead. Dependents can become confused as to which of a large number of channels to subscribe to. Multiple channels require multiple senders and receivers, perhaps leading to lots of threads checking lots of channels that are usually empty. So, creating yet more channels may not be such a good idea.
更好的方法可能是在同一通道上发送地址更改消息和信用评级更改消息,因为它们都涉及对客户的更改,并且对一种更改感兴趣的应用程序也可能对其他更改感兴趣。然而,单独的产品外渠道仍然是一个好主意,因为对客户感兴趣的应用程序可能对产品不感兴趣,反之亦然。
What may work better is to send both the address-changed and credit-rating-changed messages on the same channel, since they both concern changes to the customer and an application interested in one kind of change may be interested in the others as well. Yet, a separate out-of-product channel is still a good idea, since applications interested in customers may not be interested in products, and vice versa.
地址更改和信用评级更改的消息具有不同的格式,但数据类型通道告诉我们,要在同一通道上,消息必须具有相同的格式。对于 XML,这意味着所有消息必须具有相同的根元素类型,但可能可以具有不同的可选嵌套元素。因此,统一的客户更改消息可能如下所示:
The address-changed and credit-rating-changed messages have different formats, yet Datatype Channel tells us that to be on the same channel, the messages must have the same format. With XML, this means that all of the messages must have the same root element type but perhaps can have different optional nested elements. So, unified customer-changed messages might look like this:
<CustomerChange customer_id="12345">
<地址变更>
<旧地址>
<街>华尔街123号</街>
<城市>纽约</城市>
<州>纽约州</州>
<邮编>10005</邮编>
</旧地址>
<新地址>
<街道>日落大道321号</街道>
<城市>洛杉矶</城市>
<州>加利福尼亚州</州>
<邮编>90012</邮编>
</新地址>
</地址更改>
</客户变更>
<CustomerChange customer_id="12345">
<信用评级变更>
<旧评级>AAA</旧评级>
<新评级>BBB</新评级>
</信用评级变更>
</客户变更>
<CustomerChange customer_id="12345">
<AddressChange>
<OldAddress>
<Street>123 Wall Street</Street>
<City>New York</City>
<State>NY</State>
<Zip>10005</Zip>
</OldAddress>
<NewAddress>
<Street>321 Sunset Blvd</Street>
<City>Los Angeles</City>
<State>CA</State>
<Zip>90012</Zip>
</NewAddress>
</AddressChange>
</CustomerChange>
<CustomerChange customer_id="12345">
<CreditRatingChange>
<OldRating>AAA</OldRating>
<NewRating>BBB</NewRating>
</CreditRatingChange>
</CustomerChange>
可能仍然存在这样的问题:对地址更改感兴趣的运输应用程序对信用评级更改不感兴趣,而计费应用程序则对相反的情况感兴趣。这些应用程序可以使用选择性消费者来仅获取感兴趣的消息。如果事实证明选择性消费者很复杂,并且消息传递系统可以轻松支持更多渠道,那么也许单独的渠道会更好。
There may still be the problem that shipping applications interested in address changes are not interested in credit rating changes, and billing applications are interested in the opposite. These applications can use Selective Consumers to get only the messages of interest. If selective consumers prove to be complicated and a messaging system can easily support more channels, then perhaps separate channels would be better after all.
与企业架构和设计中的许多问题一样,没有简单的答案,需要进行大量的权衡。与任何消息通道一样,发布-订阅通道的目标是帮助确保观察者只收到他们需要的通知,而不会出现单独通道的爆炸性增长,并且不会对典型的观察者造成负担,因为大量线程运行着大量消费者,监控大量数据的频道。
As with many issues in enterprise architecture and design, there are no simple answers and lots of trade-offs. With Publish-Subscribe Channel, as with any message channel, the goal is to help ensure that the observers receive only the notifications they need, without an explosion of separate channels and without taxing the typical observer with lots of threads running lots of consumers monitoring lots of channels.
此示例表明发布-订阅通道是观察者模式的一种实现,使得该模式在分布式环境中更易于使用。当使用通道时,Subject.Notify()和Observer.Update()变得更加简单,因为他们所要做的就是发送和接收消息。消息系统负责分发和并发,同时使远程通知更加可靠。推送模型比拉取模型更简单且通常更高效,特别是对于分布式通知和消息传递。然而,拉模型也可以使用消息传递来实现。在大量数据可能发生变化的复杂应用程序中,为每个可能变化的不同事物创建一个通道可能很诱人,但使用相同的通道将类似的通知传输给相同的观察者通常更实际。即使您的应用程序不需要其他任何消息传递,如果它们需要相互通知更改,那么它也可能值得使用消息传递只是为了让您可以利用发布-订阅通道。
This example shows that Publish-Subscribe Channels are an implementation of the Observer pattern that makes the pattern much easier to use in distributed environments. When a channel is used, Subject.Notify() and Observer.Update() become much simpler because all they have to do is send and receive messages. The messaging system takes care of distribution and concurrency while making the remote notification more reliable. The push model is simpler and often more efficient than the pull model, especially for distributed notification and with messaging. Yet, the pull model can also be implemented using messaging. In complex applications where lots of data can change, it may be tempting to create a channel for every different thing that can change, but it's often more practical to use the same channel to transmit similar notifications going to the same observers. Even if your applications don't need messaging for anything else, if they need to notify each other of changes, it may well be worth using Messaging just so you can take advantage of Publish-Subscribe Channels.
在第 3 章“消息系统”中,我们讨论了如何使用消息路由器将消息源与消息的最终目的地解耦。本章详细介绍了特定类型的消息路由器,以解释如何为集成解决方案提供路由和代理功能。大多数模式都是消息路由器模式的改进,而其他模式则结合多个消息路由器来解决更复杂的问题。因此,我们可以将消息路由模式分为以下几组:
In Chapter 3, "Messaging Systems," we discussed how a Message Router can be used to decouple a message source from the ultimate destination of the message. This chapter elaborates on specific types of Message Routers to explain how to provide routing and brokering ability to an integration solution. Most patterns are refinements of the Message Router pattern, while others combine multiple Message Routers to solve more complex problems. Therefore, we can categorize the message routing patterns into the following groups:
简单路由器是消息路由器的变体,它将消息从一个入站通道路由到一个或多个出站通道。
Simple Routers are variants of the Message Router and route messages from one inbound channel to one or more outbound channels.
组合路由器组合多个简单路由器来创建更复杂的消息流。
Composed Routers combine multiple simple routers to create more complex message flows.
架构模式描述了基于消息路由器的架构风格。
Architectural Patterns describe architectural styles based on Message Routers.
基于内容的路由器检查消息的内容并根据消息的内容将其路由到另一个通道。使用这样的路由器使消息生产者能够将消息发送到单个通道,并将其留给基于内容的路由器将它们路由到正确的目的地。这减轻了发送应用程序的这项任务,并避免将消息生产者耦合到特定的目标通道。
The Content-Based Router inspects the content of a message and routes it to another channel based on the content of the message. Using such a router enables the message producer to send messages to a single channel and leave it to the Content-Based Router to route them to the proper destination. This alleviates the sending application from this task and avoids coupling the message producer to specific destination channels.
消息过滤器是基于内容的路由器的一种特殊形式。它检查消息内容,仅当消息内容符合特定条件时才将消息传递到另一个通道。否则,它会丢弃该消息。消息过滤器执行的功能与选择性消费者的功能非常相似,主要区别在于消息过滤器是消息传递系统的一部分,将合格的消息路由到另一个通道,而选择性消费者内置于消息端点中端点中。
A Message Filter is a special form of a Content-Based Router. It examines the message content and passes the message to another channel only if the message content matches certain criteria. Otherwise, it discards the message. A Message Filter performs a function that is very similar to that of a Selective Consumer with the key difference that a Message Filter is part of the messaging system, routing qualifying messages to another channel, whereas a Selective Consumer is built into a Message Endpoint.
基于内容的路由器和消息过滤器实际上可以解决类似的问题。基于内容的路由器根据基于内容的路由器中编码的标准将消息路由到正确的目的地。通过使用发布-订阅通道和一组消息过滤器(每个潜在接收者一个)可以实现等效行为。每个消息过滤器都会消除与特定目标的条件不匹配的消息。基于内容的路由器预测性地路由到单个通道,因此具有完全控制权,但它也依赖于所有可能的目标通道的列表。相比之下,消息过滤器阵列进行反应性过滤,将路由逻辑分布在许多消息过滤器上,但避免依赖于所有可能目的地的单个组件。消息过滤器模式中更详细地描述了这些解决方案之间的权衡。
A Content-Based Router and a Message Filter can actually solve a similar problem. A Content-Based Router routes a message to the correct destination based on the criteria encoded in the Content-Based Router. Equivalent behavior can be achieved by using a Publish-Subscribe Channel and an array of Message Filters, one for each potential recipient. Each Message Filter eliminates the messages that do not match the criteria for the specific destination. The Content-Based Router routes predictively to a single channel and therefore has total control, but it is also dependent on the list of all possible destination channels. In contrast, the Message Filter array filters reactively, spreading the routing logic across many Message Filters but avoiding a single component that is dependent on all possible destinations. The trade-off between these solutions is described in more detail in the Message Filter pattern.
基本消息路由器使用固定规则来确定传入消息的目的地。当我们需要更大的灵活性时,动态路由器会非常有用。该路由器允许通过将控制消息发送到指定的控制端口来修改路由逻辑。动态路由器的动态特性可以与大多数形式的消息路由器相结合。
A basic Message Router uses fixed rules to determine the destination of an incoming message. Where we need more flexibility, a Dynamic Router can be very useful. This router allows the routing logic to be modified by sending control messages to a designated control port. The dynamic nature of the Dynamic Router can be combined with most forms of the Message Router.
第 4 章“消息传递通道”介绍了点对点通道和发布-订阅通道的概念。有时,您需要向多个收件人发送一封邮件,但希望保持对收件人的控制。收件人列表可以让您做到这一点。本质上,收件人列表是一个基于内容的路由器,可以将单个消息路由到多个目标通道。
Chapter 4, "Messaging Channels," introduced the concepts of Point-to-Point Channel and Publish-Subscribe Channel. Sometimes, you need to send a message to more than one recipient but want to maintain control over the recipients. The Recipient List allows you do just that. In essence, a Recipient List is a Content-Based Router that can route a single message to more than one destination channel.
有些消息包含单个项目的列表。您如何单独处理这些项目?使用分离器大消息拆分为单独的消息。然后,每条消息都可以进一步路由并单独处理。
Some messages contain lists of individual items. How do you process these items individually? Use a Splitter to split the large message into individual messages. Each message can then be routed further and processed individually.
但是,您可能需要将拆分器创建的消息重新组合回单个消息。这是聚合器执行的功能之一。聚合器可以接收消息流、识别相关消息并将它们组合成单个消息。与其他路由模式不同,聚合器是一个有状态的消息路由器,因为它必须在内部存储消息,直到满足特定条件。这意味着聚合器在发布消息之前可以使用多条消息。
However, you may need to recombine the messages that the Splitter created back into a single message. This is one of the functions an Aggregator performs. An Aggregator can receive a stream of messages, identify related messages, and combine them into a single message. Unlike the other routing patterns, the Aggregator is a stateful Message Router because it has to store messages internally until specific conditions are fulfilled. This means that an Aggregator can consume multiple messages before it publishes a message.
因为我们使用消息传递来连接在多台计算机上运行的应用程序或组件,所以可以并行处理多个消息。例如,多个进程可能会消耗来自单个通道的消息。其中一个进程可能比另一个进程执行得更快,从而导致消息处理无序。然而,某些组件(例如基于分类帐的系统)依赖于各个消息的正确顺序。重排序器将失序的消息重新按顺序排列。重排序器也是一个有状态的消息路由器,因为它可能需要在内部存储大量消息,直到完成序列的消息到达。与聚合器不同不过,重排序器最终会发布与其消耗的相同数量的消息。
Because we use messaging to connect applications or components running on multiple computers, multiple messages can be processed in parallel. For example, more than one process may consume messages off a single channel. One of these processes may execute faster than another, causing messages to be processed out of order. However, some componentsfor example, ledger-based systemsdepend on the correct sequence of individual messages. The Resequencer puts out-of-sequence messages back into sequence. The Resequencer is also a stateful Message Router because it may need to store a number of messages internally until the message that completes the sequence arrives. Unlike the Aggregator, though, the Resequencer ultimately publishes the same number of messages it consumed.
下表总结了消息路由器变体的属性(我们没有将动态路由器作为单独的替代方案包括在内,因为任何路由器都可以实现为动态变体):
The following table summarizes the properties of the Message Router variants (we did not include the Dynamic Router as a separate alternative because any router can be implemented as a dynamic variant):
图案 Pattern | 消费消息数 Number of Messages Consumed | 发布消息数 Number of Messages Published | 有状态? Stateful? | 评论 Comment |
|---|---|---|---|---|
基于内容的路由器 Content-Based Router | 1 1 | 1 1 | 没有(大部分) No (mostly) | |
筛选 Filter | 1 1 | 0 或 1 0 or 1 | 没有(大部分) No (mostly) | |
收件人名单 Recipient List | 1 1 | 多个(包括0) multiple (incl. 0) | 不 No | |
音序器 Sequencer | 1 1 | 多种的 multiple | 不 No | |
聚合器 Aggregator | 多种的 multiple | 1 1 | 是的 Yes | |
重排序器 Resequencer | 多种的 multiple | 多种的 multiple | 是的 Yes | 发布与消耗的数量相同的数量 Publishes same number it consumes |
管道和过滤器架构的一个关键优势是我们可以将多个过滤器组合成一个更大的解决方案。组合消息处理器和分散收集结合了多个消息路由器变体以创建更全面的解决方案。这两种模式都允许我们从多个来源检索信息并将其重新组合成单个消息。组合消息处理器将单个消息拆分为多个部分,而分散-聚集将同一消息的副本发送给多个收件人。
A key advantage of the Pipes and Filters architecture is that we can compose multiple filters into a larger solution. Composed Message Processor and Scatter-Gather combine multiple Message Router variants to create more comprehensive solutions. Both patterns allow us to retrieve information from multiple sources and recombine it into a single message. The Composed Message Processor splits a single message into multiple parts, whereas the Scatter-Gather send a copy of the same message to multiple recipients.
组合消息处理器和分散-聚集都将一条消息同时路由到多个参与者,并将回复重新组合成一条消息。我们可以说这些模式管理消息的并行路由。另外两个模式管理消息的顺序路由,即通过一系列单独的步骤来路由消息。如果我们想从一个中心点控制消息的路径,我们可以使用路由单指定消息应采用的路径。此模式的工作原理就像附加到办公文档的路由表一样,由多个收件人按顺序传递它们。或者,我们可以使用Process Manager ,它为我们提供了更大的灵活性,但要求消息在每个函数之后返回到中央组件。
Both the Composed Message Processor and the Scatter-Gather route a single message to a number of participants concurrently and reassemble the replies into a single message. We can say that these patterns manage the parallel routing of a message. Two additional patterns manage the sequential routing of a message, that is, routing a message through a sequence of individual steps. If we want to control the path of a message from a central point, we can use a Routing Slip to specify the path the message should take. This pattern works just like the routing slip attached to office documents to pass them sequentially by a number of recipients. Alternatively, we can use a Process Manager, which gives us more flexibility but requires the message to return to a central component after each function.
消息路由器使我们能够使用中央消息代理构建集成解决方案。与不同的消息路由设计模式相反,该模式描述了中心辐射型架构风格。
Message Routers enable us to architect an integration solution using a central Message Broker. As opposed to the different message routing design patterns, this pattern describes a hub-and-spoke architectural style.
本章包含 12 个模式。我们如何才能轻松找到适合正确目的的正确模式?以下决策图可帮助您通过简单的是或否决策找到用于正确目的的正确模式。例如,如果您正在寻找一种简单的路由模式,一次使用一条消息但按顺序发布多条消息,则应该使用 Splitter 。 该图还有助于说明各个模式的相关程度。例如,路由表和流程管理器解决类似的问题,而消息过滤器则做一些完全不同的事情。
This chapter contains 12 patterns. How can we make it easy to find the right pattern for the right purpose? The following decision chart helps you find the right pattern for the right purpose through simple yes or no decisions. For example, if you are looking for a simple routing pattern that consumes one message at a time but publishes multiple messages in sequential order, you should use a Splitter. The diagram also helps illustrate how closely the individual patterns are related. For example, a Routing Slip and a Process Manager solve similar problems, while a Message Filter does something rather different.
假设我们正在构建一个订单处理系统。收到传入订单后,我们首先验证订单,然后验证订购的商品在仓库中是否可用。该功能由库存系统执行。这一系列处理步骤是管道和过滤器风格的完美候选者。我们创建两个过滤器,一个用于验证步骤,一个用于库存系统,并通过两个过滤器路由传入消息。然而,在许多企业集成场景中,存在多个库存系统,并且每个系统只能处理特定的物料。
Assume that we are building an order-processing system. When an incoming order is received, we first validate the order and then verify that the ordered item is available in the warehouse. This function is performed by the inventory system. This sequence of processing steps is a perfect candidate for the Pipes and Filters style. We create two filters, one for the validation step and one for the inventory system, and route the incoming messages through both filters. However, in many enterprise integration scenarios more than one inventory system exists, and each system can handle only specific items.
|
我们如何处理单个逻辑功能的实现分布在多个物理系统上的情况? How do we handle a situation in which the implementation of a single logical function is spread across multiple physical systems? |
集成解决方案连接现有应用程序,以便它们协同工作。由于许多此类应用程序在开发时并未考虑到集成,因此集成解决方案很少能找到将业务功能很好地封装在单个系统内的理想场景。例如,收购或业务合作伙伴关系通常会导致多个系统执行相同的业务功能。此外,许多充当聚合商或经销商的企业通常与执行相同功能(例如,检查库存、下订单)的多个系统进行交互。更复杂的是,这些系统可能在公司内部运行,也可能处于业务合作伙伴或附属公司的控制之下。例如,像亚马逊这样的大型电子零售商允许您订购从书籍到电锯到服装的任何商品。根据商品的类型,订单可以由不同的“幕后”商家的订单处理系统来处理。
Integration solutions connect existing applications so that they work together. Because many of these applications were developed without integration in mind, integration solutions rarely find an ideal scenario where a business function is well encapsulated inside a single system. For example, acquisitions or business partnerships often result in multiple systems performing the same business function. Also, many businesses that act as aggregators or resellers typically interface with multiple systems that perform the same functions (e.g., check inventory, place order). To make matters more complicated, these systems may be operated within the company or may be under the control of business partners or affiliates. For example, large e-tailers like Amazon allow you to order anything from books to chainsaws to clothing. Depending on the type of item, the order may be processed by a different "behind-the-scenes" merchant's order processing systems.
假设该公司正在销售小部件和小工具,并且有两个库存系统:一个用于小部件,另一个用于小工具。我们还假设每个项目都由唯一的项目编号标识。当公司收到订单时,需要根据订购的商品类型来决定由哪个库存系统接收订单。我们可以根据订购的商品类型为传入订单创建单独的渠道。然而,这需要客户了解我们的内部系统架构,而实际上他们可能甚至不知道我们区分小部件和小工具。因此,我们应该向集成解决方案的其余部分(包括客户)隐藏业务功能的实现分布在多个系统中的事实。所以,
Let's assume that the company is selling widgets and gadgets and has two inventory systems: one for widgets and one for gadgets. Let's also assume that each item is identified by a unique item number. When the company receives an order, it needs to decide which inventory system should receive the order based on the type of item ordered. We could create separate channels for incoming orders based on the type of item ordered. However, this would require the customers to know our internal system architecture when in fact they may not even be aware that we distinguish between widgets and gadgets. Therefore, we should hide the fact that the implementation of the business function is spread across multiple systems from the remainder of the integration solution, including customers. Therefore, we must expect messages for different items to arrive on the same channel.
我们可以将订单转发到所有库存系统(使用发布-订阅通道),并让每个系统决定是否可以处理该订单。这种方法使得添加新的库存系统变得容易,因为当新的库存系统上线时,我们不必更改任何现有组件。然而,这种方法假设跨多个系统进行分布式协调。如果任何系统都无法处理订单,会发生什么情况?或者是否有多个系统可以处理该订单?客户会收到重复的货件吗?此外,在许多情况下,库存系统会将无法处理的商品订单视为错误。如果是这种情况,则每个订单都会导致除一个库存系统之外的所有库存系统出现错误。很难将这些错误与“真实”错误(例如无效订单)区分开来。
We could forward the order to all inventory systems (using a Publish-Subscribe Channel ), and let each system decide whether it can handle the order. This approach makes the addition of new inventory systems easy because we do not have to change any of the existing components when a new inventory system comes online. However, this approach assumes distributed coordination across multiple systems. What happens if the order cannot be processed by any system? Or if more than one system can process the order? Will the customer receive duplicate shipments? Also, in many cases an inventory system will treat an order for an item that it cannot handle as an error. If this is the case, each order would cause errors in all inventory systems but one. It would be hard to distinguish these errors from "real" errors, such as an invalid order.
另一种方法是使用商品编号作为频道地址。每个商品都有其专用的渠道,客户可以简单地将订单发布到与商品编号关联的渠道,而无需了解小部件和小工具之间的任何内部区别。库存系统可以在所有渠道上侦听它可以处理的那些物品。此方法利用通道可寻址性将消息路由到正确的库存系统。然而,大量的项目可能很快导致通道数量激增,给系统带来运行时和管理开销的负担。为每个提供的商品创建新渠道很快就会导致混乱。
An alternative approach would be to use the item number as a channel address. Each item would have its dedicated channel, and the customers could simply publish the order to the channel associated with the item's number without having to know about any internal distinctions between widgets and gadgets. The inventory systems could listen on all the channels for those items that it can process. This approach leverages the channel addressability to route messages to the correct inventory system. However, a large number of items could quickly lead to an explosion of the number of channels, burdening the system with runtime and management overhead. Creating new channels for each item that is offered would quickly result in chaos.
我们还应该尽量减少消息流量。例如,我们可以将订单消息依次通过一个库存系统。第一个可以接受订单的系统消费该消息并处理该订单。如果它无法处理订单,则会将订单消息传递到下一个系统。这种方法消除了订单同时被多个系统接受的危险。此外,我们知道如果最后一个系统将订单传回,则该订单未被任何系统处理。然而,该解决方案确实要求系统彼此足够了解,以便将消息从一个系统传递到下一个系统。这种方法类似于责任链[ GoF]。然而,在基于消息的集成领域,通过一系列系统传递消息可能意味着巨大的开销。此外,这种方法需要各个系统之间的协作,如果某些系统由外部业务合作伙伴维护,因此不受我们的控制,则这可能不可行。
We should also try to minimize message traffic. For example, we could route the order message through one inventory system after the other. The first system that can accept the order consumes the message and processes the order. If it cannot process the order, it passes the order message to the next system. This approach eliminates the danger of orders being accepted by multiple systems simultaneously. Also, we know that the order was not processed by any system if the last system passes it back. The solution does require, however, that the systems know enough about each other to pass the message from one system to the next. This approach is similar to the Chain of Responsibility pattern [GoF]. However, in the world of message-based integration, passing messages through a chain of systems could mean significant overhead. Also, this approach would require collaboration of the individual systems, which may not be feasible if some systems are maintained by external business partners and are therefore not under our control.
总之,我们需要一种解决方案,它能够封装业务功能跨系统拆分的事实,高效地使用消息通道和消息流量,并确保订单仅由一个库存系统处理。
In summary, we need a solution that encapsulates the fact that the business function is split across systems, is efficient in its use of message channels and message traffic, and ensures that the order is handled by exactly one inventory system.
|
使用基于内容的路由器根据消息的内容将每条消息路由到正确的收件人。 Use a Content-Based Router to route each message to the correct recipient based on the message's content. |
基于内容的路由器检查消息内容并根据消息中包含的数据将消息路由到不同的通道。路由可以基于许多标准,例如字段的存在、特定字段值等等。在实现基于内容的路由器时,应特别注意使路由功能易于维护,因为路由器可能成为频繁维护的点。在更复杂的集成场景中,基于内容的路由器可以采用可配置规则引擎的形式,该引擎根据一组可配置规则计算目标通道。
The Content-Based Router examines the message content and routes the message onto a different channel based on data contained in the message. The routing can be based on a number of criteria, such as existence of fields, specific field values, and so on. When implementing a Content-Based Router, special caution should be taken to make the routing function easy to maintain, as the router can become a point of frequent maintenance. In more sophisticated integration scenarios, the Content-Based Router can take on the form of a configurable rules engine that computes the destination channel based on a set of configurable rules.
基于内容的路由器是更通用的消息路由器的一种常用形式。它使用预测路由,也就是说,它结合了所有其他系统的功能知识。这有助于高效路由,因为每个传出消息都直接发送到正确的系统。缺点是基于内容的路由器必须了解所有可能的接收者及其能力。当添加、删除或更改收件人时,每次都必须更改基于内容的路由器。这可能会成为维护的噩梦。
Content-Based Router is a frequently used form of the more generic Message Router. It uses predictive routingthat is, it incorporates knowledge of the capabilities of all other systems. This makes for efficient routing because each outgoing message is sent directly to the correct system. The downside is that the Content-Based Router has to have knowledge of all possible recipients and their capabilities. As recipients are added, removed, or changed, the Content-Based Router has to be changed every time. This can become a maintenance nightmare.
如果收件人对路由过程有更多的控制,我们就可以避免基于内容的路由器对各个收件人的依赖。这些选项可以概括为反应式过滤,因为它们允许每个参与者过滤相关消息。路由控制的分布消除了对基于内容的路由器的需要,但该解决方案通常效率较低。这些解决方案和相关的权衡在消息过滤器和路由表中有更详细的描述。
We can avoid the dependency of the Content-Based Router on the individual recipients if the recipients assume more control over the routing process. These options can be summarized as reactive filtering because they allow each participant to filter relevant messages as they come by. The distribution of routing control eliminates the need for a Content-Based Router, but the solution is generally less efficient. These solutions and associated trade-offs are described in more detail in the Message Filter and Routing Slip.
动态路由器通过让每个接收者向基于内容的路由器告知其功能,描述了基于内容的路由器和反应式过滤方法之间的折衷。基于内容的路由器维护每个接收者的能力的列表并相应地路由传入的消息。与简单的基于内容的路由器相比,我们为这种灵活性付出的代价是解决方案的复杂性以及调试此类系统的难度。
The Dynamic Router describes a compromise between the Content-Based Router and the reactive filtering approach by having each recipient inform the Content-Based Router of its capabilities. The Content-Based Router maintains a list of each recipient's capabilities and routes incoming messages accordingly. The price we pay for this flexibility is the complexity of the solution and the difficulty of debugging such a system when compared to a simple Content-Based Router.
|
示例: 使用 C# 和 MSMQ 的基于内容的路由器 Example: Content-Based Router with C# and MSMQ 此代码示例演示了一个非常简单的基于内容的路由器,它根据消息正文中的第一个字符来路由消息。如果正文以 W 开头,则路由器将消息路由到 widgetQueue ; 如果以 G 开头,则进入 gadgetQueue 。 如果两者都不是,则路由器将其发送到dunnoQueue 。该队列实际上是无效消息通道的一个示例。该路由器是无状态的,也就是说,它在做出路由决策时不会“记住”任何先前的消息。 This code example demonstrates a very simple Content-Based Router that routes messages based on the first character in the message body. If the body text starts with W, the router routes the message to the widgetQueue; if it starts with G, it goes to the gadgetQueue. If it is neither, the router sends it to the dunnoQueue. This queue is actually an example of an Invalid Message Channel. This router is stateless, that is, it does not "remember" any previous messages when making the routing decision. 类 ContentBaseRouter { 受保护的消息队列inQueue; 受保护的消息队列widgetQueue; 受保护的消息队列gadgetQueue; 受保护的消息队列不知道队列; 公共ContentBasedRouter(消息队列inQueue,消息队列 class ContentBasedRouter { protected MessageQueue inQueue; protected MessageQueue widgetQueue; protected MessageQueue gadgetQueue; protected MessageQueue dunnoQueue; public ContentBasedRouter(MessageQueue inQueue, MessageQueue widgetQueue, MessageQueue gadgetQueue, MessageQueue dunnoQueue) { this.inQueue = inQueue; this.widgetQueue = widgetQueue; this.gadgetQueue = gadgetQueue; this.dunnoQueue = dunnoQueue; inQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnMessage); inQueue.BeginReceive(); } private void OnMessage(Object source, ReceiveCompletedEventArgs asyncResult) { MessageQueue mq = (MessageQueue)source; mq.Formatter = new System.Messaging.XmlMessageFormatter (new String[] {"System.String,mscorlib"}); Message message = mq.EndReceive(asyncResult.AsyncResult); if (IsWidgetMessage(message)) widgetQueue.Send(message); else if (IsGadgetMessage(message)) gadgetQueue.Send(message); else dunnoQueue.Send(message); mq.BeginReceive(); } protected bool IsWidgetMessage (Message message) { String text = (String)message.Body; return (text.StartsWith("W")); } protected bool IsGadgetMessage (Message message) { String text = (String)message.Body; return (text.StartsWith("G")); } } 该示例通过将方法OnMessage注册为到达inQueue 的消息的处理程序来使用事件驱动的消息使用者。这会导致 .NET Framework 为到达inQueue调用OnMessage方法。消息队列Formatter属性告诉框架期望什么类型的消息;在我们的示例中,我们只处理简单的字符串消息。OnMessage确定消息的路由位置,并通过调用以下函数告诉 .NET 它已准备好接收下一条消息:确定消息的路由位置,并通过调用BeginReceive告诉 .NET 它已准备好接收下一条消息队列上的方法。为了使代码保持最少,这个简单的路由器不是事务性的:如果路由器在消耗来自输入通道的消息之后并将其发布到输出通道之前崩溃,我们将丢失一条消息。后面的章节解释了如何使端点具有事务性(请参阅事务性客户端)。 The example uses an event-driven message consumer by registering the method OnMessage as the handler for messages arriving on the inQueue. This causes the .NET Framework to invoke the method OnMessage for every message that arrives on the inQueue. The message queue Formatter property tells the framework what type of message to expect; in our example, we only deal with simple string messages. OnMessage figures out where to route the message and tells .NET that it is ready for the next message by calling the BeginReceive method on the queue. In order to keep the code to a minimum, this simple router is not transactional: If the router crashes after it consumed a message from the input channel and before it published it to the output channel, we would lose a message. Later chapters explain how to make endpoints transactional (see Transactional Client ). |
|
示例: TIBCO MessageBroker Example: TIBCO MessageBroker 消息路由是一种常见的需求,大多数 EAI 工具套件都提供内置工具来简化路由逻辑的构建。例如,在 C# 示例中,我们必须编写逻辑以从传入队列中读取消息、反序列化它、分析它,并将其重新发布到正确的传出通道。在许多 EAI 工具中,这种类型的逻辑可以通过简单的拖放操作来实现,而不是通过编写代码。唯一要编写的代码是基于内容的路由器的实际决策逻辑。 Message routing is such a common need that most EAI tool suites provide built-in tools to simplify the construction of the routing logic. For example, in the C# example we had to code the logic to read a message off the incoming queue, deserialize it, analyze it, and republish it to the correct outgoing channel. In many EAI tools this type of logic can be implemented with simple drag-and-drop operations instead of by writing code. The only code to write is the actual decision logic for the Content-Based Router. TIBCO ActiveEnterprise 套件就是实现消息路由的此类 EAI 工具之一。该套件包括 TIB/MessageBroker,旨在创建包含转换和路由功能的简单消息流。在 TIB/MessageBroker 中实现时,根据项目编号的第一个字母路由传入消息的相同小部件路由器如下所示: One such EAI tool that implements message routing is the TIBCO ActiveEnterprise suite. The suite includes TIB/MessageBroker, which is designed to create simple message flows that include transformation and routing functions. The same widget router that routes incoming messages based on the first letter of the item number looks like this when implemented in TIB/MessageBroker: 我们可以从左到右阅读消息流。左侧的组件(由指向右侧的三角形表示)是订阅者组件,用于消费来自通道router.in的消息。通道名称在图中未显示的属性框中指定。消息内容被定向到消息发布者(由屏幕右侧的三角形表示)。从订阅者的数据输出到发布者的消息输入的直接线路表示基于内容的路由器不会修改消息正文。为了确定正确的输出通道,该函数ComputeSubject(中间)分析消息内容。该函数使用所谓的字典(图中标记为“Map”)作为消息内容和目标通道名称之间的转换表。该字典配置有以下值: We can read the message flow from left to right. The component on the left (represented by a triangle pointing to the right) is the subscriber component that consumes messages off the channel router.in. The channel name is specified in a properties box not shown in this figure. The message content is directed to the message publisher (represented by the triangle on the right side of the screen). The direct line from the Data output of the subscriber to the Message input of the publisher represents the fact that a Content-Based Router does not modify the message body. In order to determine the correct output channel, the function ComputeSubject (in the middle) analyzes the message content. The function uses a so-called dictionary (labeled "Map" in the figure) as a translation table between message contents and the destination channel name. The dictionary is configured with the following values:
ComputeSubject函数使用传入消息的订单项编号的第一个字母从字典中查找目标通道。为了形成输出通道的完整名称,它将字典结果附加到字符串router.out ,以形成类似router.out.widget 的通道名称。计算结果被传递到右侧的发布者组件,用作通道的名称。因此,任何商品编号以 G 开头的订单商品都会被路由到通道router.out.gadget,而任何商品编号以 W 开头的商品都会被路由到通道router.out.widget。 The ComputeSubject function uses the first letter of the incoming message's order item number to look up the destination channel from the dictionary. To form the complete name of the output channel, it appends the dictionary result to the string router.out, to form a channel name like router.out.widget. The result of this computation is passed to the publisher component on the right to be used as the name of the channel. As a result, any order item whose item number starts with a G is routed to the channel router.out.gadget, whereas any item whose item number starts with a W is routed to the channel router.out.widget. ComputeSubject函数的 TIBCO 实现如下所示: The TIBCO implementation of the ComputeSubject function looks like this: concat("router.out.",DGet(地图,Upper(Left(OrderItem.ItemNumber,1))))
concat("router.out.",DGet(map,Upper(Left(OrderItem.ItemNumber,1))))
该函数提取订单号的第一个字母(使用Left函数)并将其转换为大写字母(使用Upper函数)。该函数使用结果作为字典的键来检索传出通道的名称(使用DGet函数)。 The function extracts the first letter of the order number (using the Left function) and converts it to uppercase (using the Upper function). The function uses the result as the key to the dictionary to retrieve the name of the outgoing channel (using the DGet function). 此示例展示了商业 EAI 工具的优势。我们不需要编写几十行代码,只需编写一个函数即可实现相同的小部件路由器功能。此外,我们还免费获得事务性、线程管理和系统管理等功能。但这个例子也凸显了呈现使用图形工具创建的解决方案的困难。我们不得不使用屏幕截图来描述解决方案。许多重要的设置隐藏在屏幕上未显示的属性字段中。这使得记录使用图形开发工具构建的解决方案变得困难。 This example demonstrates the strengths of commercial EAI tools. Instead of a few dozen lines of code, we need to code only a single function to implement the same widget router functionality. Plus, we get features like transactionality, thread management, and systems management for free. But this example also highlights the difficulties of presenting a solution created with graphical tools. We had to relegate to screenshots to describe the solution. Many important settings are hidden in property fields that are not shown on the screen. This can make it difficult to document a solution built using graphical development tools. |
继续订单处理示例,假设公司管理层决定向大客户发布价格变化和促销信息。每当商品价格发生变化时,我们都会发送消息通知客户。如果我们正在进行特别促销,例如 11 月份所有小部件 10% 的折扣,我们也会这样做。有些客户可能有兴趣接收仅与特定商品相关的价格更新或促销信息。例如,如果我主要购买小工具,我可能对了解小工具是否打折不感兴趣。
Continuing with the order processing example, let's assume that company management decided to publish price changes and promotions to large customers. We would like to send a message to notify the customer whenever the price for an item changes. We do the same if we are running a special promotion, such as 10 percent off all widgets in the month of November. Some customers may be interested in receiving price updates or promotions related only to specific items. For example, if I purchase primarily gadgets, I may not be interested in knowing whether or not widgets are on sale.
|
组件如何避免接收无趣的消息? How can a component avoid receiving uninteresting messages? |
组件仅接收相关消息的最基本方法是仅订阅那些携带相关消息的通道。此选项利用了发布-订阅通道的固有路由功能。 组件仅接收那些通过该组件订阅的通道传播的消息。例如,我们可以创建一个用于小部件更新的通道,另一个用于小工具更新的通道。然后,客户可以自由订阅一个或另一个频道或两者。这样做的优点是新订户无需对系统进行任何更改即可加入。但是,订阅发布-订阅频道通常仅限于简单的二元条件:如果组件订阅了某个通道,它将接收该通道上的所有消息。实现更细粒度的唯一方法是创建更多通道。如果我们处理多个参数的组合,通道的数量可能会迅速爆炸。例如,如果我们想让消费者接收所有宣布所有小部件或小工具降价超过 5%、10% 或 15% 的消息,我们就需要 6 个(2 个商品类型乘以 3 个阈值)渠道。这种方法最终将变得难以管理,并且由于分配的通道数量巨大而消耗大量资源。因此,我们需要寻找一种比频道订阅更灵活的解决方案。
The most basic way for a component to receive only relevant messages is to subscribe only to those channels that carry relevant messages. This option leverages the inherent routing abilities of Publish-Subscribe Channels. A component receives only those messages that travel through channels to which the component subscribes. For example, we could create one channel for widget updates and another one for gadget updates. Customers would then be free to subscribe to one or the other channel or both. This has the advantage that new subscribers can join in without requiring any changes to the system. However, subscription to Publish-Subscribe Channel is generally limited to a simple binary condition: If a component subscribes to a channel, it receives all messages on that channel. The only way to achieve finer granularity is to create more channels. If we are dealing with a combination of multiple parameters, the number of channels can quickly explode. For example, if we want to allow consumers to receive all messages that announce all price cuts of widgets or gadgets by more than 5 percent, 10 percent, or 15 percent, we already need six (2 item types multiplied by 3 threshold values) channels. This approach would ultimately become difficult to manage and will consume significant resources due to the large number of allocated channels. So, we need to look for a solution that allows for more flexibility than channel subscription allows.
我们还需要一个能够适应频繁变化的解决方案。例如,我们可以修改基于内容的路由器以将消息路由到多个目的地(收件人列表中描述的概念)。该预测路由器仅向每个接收者发送相关消息,因此接收者无需采取任何额外步骤。然而,现在我们给消息发起者带来了维护每个订阅者的偏好的负担。如果收件人列表或其偏好快速变化,该解决方案将成为维护的噩梦。
We also need a solution that can accommodate frequent change. For example, we could modify a Content-Based Router to route the message to more than one destination (a concept described in the Recipient List ). This predictive router sends only relevant messages to each recipient so that the recipient does not have to take any extra steps. However, now we burden the message originator with maintaining the preferences for each and every subscriber. If the list of recipients or their preferences change quickly, this solution would prove to be a maintenance nightmare.
我们可以简单地将更改广播到所有组件,并期望每个组件过滤掉不需要的消息。然而,这种方法假设我们可以控制实际组件。在许多集成场景中,情况并非如此,因为我们处理的是打包应用程序、遗留应用程序或不受我们组织控制的应用程序。
We could simply broadcast the changes to all components and expect each component to filter out the undesirable messages. However, this approach assumes that we have control over the actual component. In many integration scenarios this is not the case because we deal with packaged applications, legacy applications, or applications that are not under the control of our organization.
|
使用一种特殊的消息路由器(消息过滤器),根据一组标准从通道中消除不需要的消息。 Use a special kind of Message Router, a Message Filter, to eliminate undesired messages from a channel based on a set of criteria. |
消息过滤器是具有单个输出通道的消息路由器。如果传入消息的内容与消息过滤器指定的条件匹配,则消息将被路由到输出通道。如果消息内容不符合条件,则该消息将被丢弃。
The Message Filter is a Message Router with a single output channel. If the content of an incoming message matches the criteria specified by the Message Filter, the message is routed to the output channel. If the message content does not match the criteria, the message is discarded.
在我们的示例中,我们将定义一个发布-订阅频道,每个客户都可以自由收听。然后,客户可以使用消息过滤器根据他或她选择的标准(例如商品类型或价格变化幅度)消除消息。
In our example we would define a single Publish-Subscribe Channel that each customer is free to listen on. The customer can then use a Message Filter to eliminate messages based on criteria of his or her choosing, such as the type of item or the magnitude of the price change.
消息过滤器可以被描述为基于内容的路由器的特殊情况,它将消息路由到输出通道或空通道,即丢弃发布到它的任何消息的通道。这种通道的用途类似于许多操作系统中存在的/dev/null目标或Null 对象[ PLoPD3 ]。
The Message Filter can be portrayed as a special case of a Content-Based Router that routes the message either to the output channel or the null channel, a channel that discards any message published to it. The purpose of such a channel is similar to the /dev/null destination present in many operating systems or to a Null Object [PLoPD3].
小部件和小工具示例描述了无状态消息过滤器:消息过滤器检查单个消息并仅根据该消息中包含的信息决定是否传递它。因此,消息过滤器不需要跨消息维护状态,被认为是无状态的。无状态组件的优点是它们允许我们并行运行组件的多个实例以加快处理速度。然而,消息过滤器不必是无状态的。例如,在某些情况下,消息过滤器需要跟踪消息历史记录。一个常见的场景是使用消息过滤器以消除重复的消息。假设每条消息都有唯一的消息标识符,消息过滤器将存储过去消息的标识符,以便它可以通过将每条消息的标识符与存储的标识符列表进行比较来识别重复消息。
The widget and gadget example describes a stateless Message Filter: The Message Filter inspects a single message and decides whether or not to pass it on based solely on information contained in that message. Therefore, the Message Filter does not need to maintain state across messages and is considered stateless. Stateless components have the advantage that they allow us to run multiple instances of the component in parallel to speed up processing. However, a Message Filter does not have to be stateless. For example, there are situations in which the Message Filter needs to keep track of the message history. A common scenario is the use of a Message Filter to eliminate duplicate messages. Assuming that each message has a unique message identifier, the Message Filter would store the identifiers of past messages so that it can recognize a duplicate message by comparing each message's identifier with the list of stored identifiers.
一些消息传递系统在消息传递基础设施内合并了消息过滤器的各个方面。例如,某些发布-订阅系统允许您定义发布-订阅通道的层次结构。许多发布-订阅系统(包括大多数 JMS 实现)都允许这样做。例如,可以将促销活动发布到频道wgco.update.promotion.widget。然后,订阅者可以使用通配符来订阅特定的消息子集;例如,如果订阅者收听主题wgco.update.*.widget,他将收到与小部件相关的所有更新(促销和价格变化)。其他订阅者可能会收听 wgco.update.promotion.*,这将提供与小部件和小工具相关的所有促销活动,但没有价格变化。通道层次结构使我们能够通过附加限定参数来细化通道的语义,这样客户就可以通过指定附加条件作为通道名称的一部分来过滤消息,而不是订阅所有更新。然而,与消息过滤器相比,分层通道命名提供的灵活性仍然有限。例如,消息过滤器可以决定仅在价格变化超过 11.5% 时才传递价格变化消息,而这很难通过通道名称来表达。
Some messaging systems incorporate aspects of a Message Filter inside the messaging infrastructure. For example, some publish-subscribe systems allow you to define a hierarchical structure for Publish-Subscribe Channels. Many publish-subscribe systems, including most JMS implementations, allow this. For example, one can publish promotions to the channel wgco.update.promotion.widget. A subscriber can then use wildcards to subscribe to a specific subset of messages; for example, if a subscriber listens to the topic wgco.update.*.widget, he would receive all updates (promotions and price changes) related to widgets. Another subscriber may listen to wgco.update.promotion.*, which would deliver all promotions related to widgets and gadgets, but no price changes. The channel hierarchy lets us refine the semantics of a channel by appending qualifying parameters, so that instead of a customer subscribing to all updates, customers can filter messages by specifying additional criteria as part of the channel name. However, the flexibility provided by the hierarchical channel naming is still limited when compared to a Message Filter. For example, a Message Filter could decide to pass on a price change message only if the price changed by more than 11.5 percent, something that would be hard to express by means of channel names.
其他消息传递系统为接收应用程序内的选择性消费者提供 API 支持。消息选择器是在应用程序查看消息之前评估传入消息内的标头或属性元素的表达式。如果条件的计算结果不为 true,则该消息将被忽略并且不会传递到应用程序逻辑。消息选择器充当应用程序中内置的消息过滤器。虽然使用消息选择器仍然需要修改应用程序(这在 EAI 中通常是不可能的),但选择规则的执行内置于消息传递基础结构中。消息过滤器a和 a之间的一个重要区别Selective Consumer是指使用Selective不会消费不符合指定条件的消息。另一方面,消息过滤器从输入通道中删除所有消息,仅将那些符合指定条件的消息发布到输出通道。
Other messaging systems provide API support for Selective Consumers inside the receiving application. Message selectors are expressions that evaluate header or property elements inside an incoming message before the application gets to see the message. If the condition does not evaluate to true, the message is ignored and not passed on to the application logic. A message selector acts as a Message Filter that is built into the application. While the use of a message selector still requires you to modify the application (something that is often not possible in EAI), the execution of the selection rules is built into the messaging infrastructure. One important difference between a Message Filter and a Selective Consumer is that a consumer using a Selective Consumer does not consume messages that do not match the specified criteria. On the other hand, a Message Filter removes all messages from the input channel, publishing to the output channel only those that match the specified criteria.
由于选择性消费者向消息传递基础设施注册过滤器表达式,因此基础设施能够根据过滤器标准做出智能的内部路由决策。我们假设消息接收者位于与消息发送者不同的网段上(甚至跨越 Internet)。如果只是为了发现我们想要丢弃该消息而将消息一直路由到消息过滤器,那将是相当浪费的。另一方面,我们希望使用消息过滤器机制,以便收件人能够控制消息路由,而不是中央消息路由器。如果消息过滤器是消息传递基础设施向消息订阅者提供的 API 的一部分,基础设施可以自由地将过滤器表达式传播到更接近源的位置。这将保持对消息订阅者进行控制的初衷,但允许消息传递基础设施避免不必要的网络流量。此行为类似于动态收件人列表。
Because the Selective Consumer registers the filter expression with the messaging infrastructure, the infrastructure is able to make smart internal routing decisions based on the filter criteria. Let's assume that the message receiver sits on a different network segment from the message originator (or even across the Internet). It would be rather wasteful to route the message all the way to the Message Filter just to find out that we want to discard the message. On the other hand, we want to use a Message Filter mechanism so that the recipients have control over the message routing instead of a central Message Router. If the Message Filter is part of the API that the messaging infrastructure provides to the message subscriber, the infrastructure is free to propagate the filter expression closer to the source. This will maintain the original intent of keeping control with the message subscriber, but allows the messaging infrastructure to avoid unnecessary network traffic. This behavior resembles that of a dynamic Recipient List.
我们可以使用连接到一组消息过滤器的发布-订阅通道来消除不需要的消息,以实现与基于内容。下图说明了这两个选项:
We can use a Publish-Subscribe Channel connected to a set of Message Filters who eliminate unwanted messages to implement functionality equivalent to that of a Content-Based Router. The following diagrams illustrate the two options:
选项 1:使用基于内容的路由器
Option 1: Using a Content-Based Router
在这个简单的示例中,我们有两个接收者:接收者Gadgets只对小工具消息感兴趣,而接收者Widgets只对小工具消息感兴趣。基于内容的路由器评估每条消息的内容并将其预测性地路由到适当的接收者。
In this simple example, we have two receivers: receiver Gadgets is only interested in gadget messages, while receiver Widgets is only interested in widget messages. The Content-Based Router evaluates each message's content and routes it predictively to the appropriate receiver.
选项 2:使用广播通道和一组消息过滤器
Option 2: Using a Broadcast Channel and a Set of Message Filters
第二个选项将消息广播到发布-订阅通道。每个收件人都配备了消息过滤器,以消除不需要的消息。例如,小部件接收器使用小部件过滤器,仅允许小部件消息通过。
The second option broadcasts the message to a Publish-Subscribe Channel. Each recipient is equipped with a Message Filter to eliminate unwanted messages. For example, the Widgets receiver employs a widget filter that lets only widget messages pass.
下表描述了两种解决方案之间的一些差异:
The following table characterizes some of the differences between the two solutions:
基于内容的路由器 Content-Based Router | 带有消息过滤器的发布-订阅通道 Publish-Subscribe Channel with Message Filters |
|---|---|
每条消息只有一个消费者接收。 Exactly one consumer receives each message. | 多个消费者可以消费一条消息。 More than one consumer can consume a message. |
中央控制和维护预测路由。 Central control and maintenancepredictive routing. | 分布式控制和维护无功滤波。 Distributed control and maintenancereactive filtering. |
路由器需要了解参与者。如果添加或删除参与者,则可能需要更新路由器。 Router needs to know about participants. Router may need to be updated if participants are added or removed. | 无需参与者了解。添加或删除参与者很容易。 No knowledge of participants required. Adding or removing participants is easy. |
通常用于商业交易,例如订单。 Often used for business transactions, e.g., orders. | 通常用于事件通知或信息性消息。 Often used for event notifications or informational messages. |
基于队列的通道通常更有效。 Generally more efficient with queue-based channels. | 通常,发布-订阅通道的效率更高。 Generally more efficient with publish-subscribe channels. |
我们如何在这两个选项之间做出决定?在某些情况下,决策是由所需的功能驱动的;例如,如果我们需要多个接收者处理同一条消息的能力,我们需要使用带有消息过滤器的发布-订阅通道。但在大多数情况下,我们决定由哪一方控制(并需要维护)路由决策。我们想要保留中央控制权还是将其外包给接收者?如果消息包含只有某些收件人才能看到的敏感数据,我们需要使用基于内容的路由器我们不想相信其他收件人会过滤掉邮件。例如,假设我们向高级客户提供特别折扣:我们不会将这些折扣发送给非高级客户,并希望他们忽略这些特别优惠。
How do we decide between the two options? In some cases, the decision is driven by the required functionality; for example, if we need the ability for multiple recipients to process the same message, we need to use a Publish-Subscribe Channel with Message Filters. In most cases, though, we decide by which party has control over (and needs to maintain) the routing decision. Do we want to keep central control or farm it out to the recipients? If messages contain sensitive data that is only to be seen by certain recipients, we need to use a Content-Based Router we would not want to trust the other recipients to filter out messages. For example, let's assume we offer special discounts to our premium customers: We would not send those to our non-premium customers and expect them to ignore these special offers.
网络流量考虑因素也可以推动决策。如果我们有一种有效的方法来广播信息(例如,在内部网络上使用 IP 多播),那么使用过滤器会非常有效,并且可以避免单个路由器的潜在瓶颈。然而,如果这些信息通过互联网路由,我们就仅限于点对点连接。在这种情况下,单个路由器的效率要高得多,因为它可以避免向所有参与者发送单独的消息,而不管他们的兴趣如何。如果我们想将控制权传递给收件人,但出于网络效率的原因需要使用路由器,我们可以使用动态收件人列表。这种类型的收件人列表充当动态路由器,允许接收者通过向路由器发送控制消息来表达他们的偏好。收件人列表将收件人首选项存储在数据库或规则库中。当消息到达时,收件人列表会将消息转发给条件与该消息匹配的所有感兴趣的收件人。
Network traffic considerations can drive the decision as well. If we have an efficient way to broadcast information (e.g., using IP multicast on an internal network), using filters can be very efficient and avoids the potential bottleneck of a single router. However, if this information is routed over the Internet, we are limited to point-to-point connections. In this case a single router is much more efficient, as it avoids sending individual messages to all participants regardless of their interests. If we want to pass control to the recipients but need to use a router for reasons of network efficiency, we can employ a dynamic Recipient List. This type of Recipient List acts as a Dynamic Router, allowing recipients to express their preferences via control messages to the router. The Recipient List stores the recipient preferences in a database or a rule base. When a message arrives, the Recipient List forwards the message to all interested recipients whose criteria match the message.
您正在使用消息路由器在多个目标之间路由消息。
You are using a Message Router to route messages between multiple destinations.
|
如何避免路由器对所有可能目的地的依赖,同时保持其效率? How can you avoid the dependency of the router on all possible destinations while maintaining its efficiency? |
消息路由器非常高效,因为它可以将消息直接路由到正确的目的地。其他消息路由解决方案,尤其是反应式过滤解决方案,效率较低,因为它们使用试错方法:例如,路由表将每条消息路由到第一个可能的目的地。如果该目的地正确,则它接受该消息;否则,消息将传递到第二个可能的目的地,依此类推。同样,基于消息过滤器的方法将消息发送给所有可能的收件人,无论他们是否感兴趣。
A Message Router is very efficient because it can route a message directly to the correct destination. Other solutions to message routing, especially reactive filtering solutions, are less efficient because they use a trial-and-error approach: For example, a Routing Slip routes each message to the first possible destination. If that destination is the correct one, it accepts the message; otherwise, the message is passed to the second possible destination and so on. Likewise, a Message Filter based approach sends the message to all possible recipients whether they are interested or not.
分布式路由解决方案还面临着消息有多个收件人或根本没有收件人的风险。除非我们使用中央路由元素,否则这两种情况都不会被发现。
Distributed routing solutions also suffer the risk that there are multiple recipients of a message or none at all. Both situations can go undetected unless we use a central routing element.
为了达到这种准确性,我们需要使用消息路由器,其中包含有关每个目的地的知识以及将消息路由到目的地的规则。但是,如果可能的目的地列表频繁更改,这可能会给消息路由器带来维护负担。
In order to achieve this accuracy, we need to use a Message Router that incorporates knowledge about each destination and the rules for routing messages to the destinations. However, this can turn the Message Router into a maintenance burden if the list of possible destinations changes frequently.
|
使用动态路由器,该路由器可以根据来自参与目标的特殊配置消息进行自我配置。 Use a Dynamic Router, a router that can self-configure based on special configuration messages from participating destinations. |
除了通常的输入和输出通道之外,动态路由器还使用额外的控制通道。在系统启动期间,每个潜在接收者都会在此控制通道上向动态路由器发送一条特殊消息,宣布其存在并列出其可以处理消息的条件。动态路由器将每个参与者的首选项存储在规则库中。当消息到达时,动态路由器会评估所有规则并将消息路由到满足规则的收件人。这样可以实现高效的预测性路由,而无需动态路由器的维护依赖关于每个潜在的接收者。
Besides the usual input and output channels, the Dynamic Router uses an additional control channel. During system startup, each potential recipient sends a special message to the Dynamic Router on this control channel, announcing its presence and listing the conditions under which it can handle a message. The Dynamic Router stores the preferences for each participant in a rule base. When a message arrives, the Dynamic Router evaluates all rules and routes the message to the recipient whose rules are fulfilled. This allows for efficient, predictive routing without the maintenance dependency of the Dynamic Router on each potential recipient.
在最基本的场景中,每个参与者在启动时向动态路由器宣布其存在和路由首选项。这要求每个参与者了解动态路由器使用的控制队列。它还要求动态路由器以持久的方式存储规则。否则,如果动态路由器发生故障并必须重新启动,它将无法恢复路由规则。或者,动态路由器可以向所有可能的参与者发送广播消息,以触发他们回复控制消息。此配置更稳健,但需要使用额外的发布-订阅通道。
In the most basic scenario, each participant announces its existence and routing preferences to the Dynamic Router at startup time. This requires each participant to be aware of the control queue used by the Dynamic Router. It also requires the Dynamic Router to store the rules in a persistent way. Otherwise, if the Dynamic Router fails and has to restart, it would not be able to recover the routing rules. Alternatively, the Dynamic Router could send a broadcast message to all possible participants to trigger them to reply with the control message. This configuration is more robust but requires the use of an additional Publish-Subscribe Channel.
增强控制通道以允许参与者向动态路由器发送订阅和取消订阅消息可能是有意义的。这将允许接收者在运行时在路由方案中添加或删除自己。
It might make sense to enhance the control channel to allow participants to send both subscribe and unsubscribe messages to the Dynamic Router. This would allow recipients to add or remove themselves from the routing scheme during runtime.
由于收件人彼此独立,动态路由器必须处理规则冲突,例如多个收件人宣布对同一类型的消息感兴趣。动态路由器可以采用多种不同的策略来解决此类冲突:
Because the recipients are independent from each other, the Dynamic Router has to deal with rules conflicts, such as multiple recipients announcing interest in the same type of message. The Dynamic Router can employ a number of different strategies to resolve such conflicts:
忽略与现有消息冲突的控制消息。此选项可确保路由规则不发生冲突。然而,路由表的状态可能取决于潜在接收者启动的顺序。如果所有接收者同时启动,这可能会导致不可预测的行为,因为所有接收者都会同时向控制队列宣布他们的首选项。
Ignore control messages that conflict with existing messages. This option assures that the routing rules are free of conflict. However, the state of the routing table may depend on the sequence in which the potential recipients start up. If all recipients start up at the same time, this may lead to unpredictable behavior because all recipients would announce their preferences at the same time to the control queue.
将消息发送给第一个符合条件的收件人。此选项允许路由表包含冲突,但会在消息传入时解决它们。
Send the message to the first recipient whose criteria match. This option allows the routing table to contain conflicts but resolves them as messages come in.
将消息发送给所有符合条件的收件人。此选项可以容忍冲突,但会将动态路由器变成收件人列表。一般来说,基于内容的路由器的行为意味着它为每条输入消息发布一条输出消息。该策略违反了该规则。
Send the message to all recipients whose criteria match. This option is tolerant of conflicts but turns the Dynamic Router into a Recipient List. Generally, the behavior of a Content-Based Router implies that it publishes one output message for each input message. This strategy violates that rule.
动态路由器的主要缺点是解决方案的复杂性和调试动态配置系统的难度。
The main liabilities of the Dynamic Router are the complexity of the solution and the difficulty of debugging a dynamically configured system.
动态路由器是另一个示例,其中基于消息的中间件执行与较低级别 IP 网络类似的功能。动态路由器的工作方式与 IP 路由中使用的动态路由表非常相似,用于在网络之间路由 IP 数据包。接收者用于配置动态路由器的协议类似于 IP 路由信息协议(RIP;有关详细信息,请参阅 [ Stevens ])。
A Dynamic Router is another example where message-based middleware performs similar functions to lower level IP networking. A Dynamic Router works very similarly to the dynamic routing tables used in IP routing to route IP packets between networks. The protocol used by the recipients to configure the Dynamic Router is analogous to the IP Routing Information Protocol (RIP; for more information see [Stevens]).
动态路由器的常见用途是面向服务的体系结构中的动态服务发现。如果客户端应用程序想要访问服务,它会向动态路由器发送一条包含服务名称的消息。动态路由器维护一个服务目录、所有服务及其名称和侦听通道的列表。路由器根据来自每个服务提供商的控制消息构建此目录。当服务请求到达时,动态路由器使用服务目录按名称查找服务,然后将消息路由到正确的通道。此设置允许客户端应用程序将命令消息发送到单个通道,而不必担心指定服务提供商的性质或位置,即使提供商发生变化也是如此。
A common use of the Dynamic Router is dynamic service discovery in service-oriented architectures. If a client application wants to access a service, it sends a message containing the name of the service to the Dynamic Router. The Dynamic Router maintains a service directory, a list of all services with their name and the channel they listen on. The router builds this directory based on control messages from each service provider. When a service request arrives, the Dynamic Router uses the service directory to look up the service by name, then routes the message to the correct channel. This setup allows the client application to send command messages to a single channel without having to worry about the nature or location of the specified service provider, even if the provider changes.
一个相关的模式,即客户端-调度程序-服务器模式 [ POSA ],允许客户端在不知道服务提供商的物理位置的情况下请求特定服务。调度程序使用注册服务列表在客户端和实现所请求服务的物理服务器之间建立连接。动态路由器执行类似的功能,但与调度程序不同,因为它可以使用比简单的表查找更智能的路由。
A related pattern, the Client-Dispatcher-Server pattern [POSA], allows a client to request a specific service without knowing the physical location of the service provider. The dispatcher uses a list of registered services to establish a connection between the client and the physical server implementing the requested service. The Dynamic Router performs a similar function but is different from the dispatcher in that it can use more intelligent routing than a simple table lookup.
|
示例: 使用 C# 和 MSMQ 的动态路由器 Example: Dynamic Router Using C# and MSMQ 此示例基于基于内容的路由器中提供的示例构建,并对其进行了增强以充当动态路由器。新组件监听两个通道:inQueue和controlQueue 。控制队列可以接收格式为X:QueueName的消息,从而使动态路由器将正文以字母 X 开头的所有消息路由到队列QueueName。 This example builds on the example presented in the Content-Based Router and enhances it to act as a Dynamic Router. The new component listens on two channels: the inQueue and the controlQueue. The control queue can receive messages of the format X:QueueName, causing the Dynamic Router to route all messages whose body text begins with the letter X to the queue QueueName. 动态路由器类 { 受保护的消息队列inQueue; 受保护的消息队列控制队列; 受保护的消息队列不知道队列; 受保护的 IDictionary 路由表 = (IDictionary)(new class DynamicRouter { protected MessageQueue inQueue; protected MessageQueue controlQueue; protected MessageQueue dunnoQueue; protected IDictionary routingTable = (IDictionary)(new Hashtable()); public DynamicRouter(MessageQueue inQueue, MessageQueue controlQueue, MessageQueue dunnoQueue) { this.inQueue = inQueue; this.controlQueue = controlQueue; this.dunnoQueue = dunnoQueue; inQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnMessage); inQueue.BeginReceive(); controlQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnControlMessage); controlQueue.BeginReceive(); } protected void OnMessage(Object source, ReceiveCompletedEventArgs asyncResult) { MessageQueue mq = (MessageQueue)source; mq.Formatter = new System.Messaging.XmlMessageFormatter (new String[] {"System.String,mscorlib"}); Message message = mq.EndReceive(asyncResult.AsyncResult); String key = ((String)message.Body).Substring(0, 1); if (routingTable.Contains(key)) { MessageQueue destination = (MessageQueue)routingTable[key]; destination.Send(message); } else dunnoQueue.Send(message); mq.BeginReceive(); } // control message format is X:QueueName as a single string protected void OnControlMessage(Object source, ReceiveCompletedEventArgs asyncResult) { MessageQueue mq = (MessageQueue)source; mq.Formatter = new System.Messaging.XmlMessageFormatter (new String[] {"System.String,mscorlib"}); Message message = mq.EndReceive(asyncResult.AsyncResult); String text = ((String)message.Body); String [] split = (text.Split(new char[] {':'}, 2)); if (split.Length == 2) { String key = split[0]; String queueName = split[1]; MessageQueue queue = FindQueue(queueName); routingTable.Add(key, queue); } else { dunnoQueue.Send(message); } mq.BeginReceive(); } protected MessageQueue FindQueue(string queueName) { if (!MessageQueue.Exists(queueName)) { return MessageQueue.Create(queueName); } else return new MessageQueue(queueName); } } 这个例子使用了一个非常简单的冲突解决机制,最后一个获胜。如果两个收件人表示有兴趣接收以字母 X 开头的消息,则只有第二个收件人会收到该消息,因为哈希表只为每个键值存储一个队列。另请注意,dunnoQueue 现在可以接收两种类型的消息:没有匹配路由规则的传入消息或与所需格式不匹配的控制消息。 This example uses a very simple conflict resolution mechanismlast one wins. If two recipients express interest in receiving messages that start with the letter X, only the second recipient will receive the message because the Hashtable stores only one queue for each key value. Also note that the dunnoQueue can now receive two types of messages: incoming messages that have no matching routing rules or control messages that do not match the required format. |
基于内容的路由器允许我们根据消息内容将消息路由到正确的系统。这个过程对于原始发送者来说是透明的,因为发起者只是将消息发送到一个通道,路由器在其中接收消息并处理所有事情。
A Content-Based Router allows us to route a message to the correct system based on message content. This process is transparent to the original sender in the sense that the originator simply sends the message to a channel, where the router picks it up and takes care of everything.
但在某些情况下,我们可能希望为该消息指定一个或多个收件人。一个常见的类比是大多数电子邮件系统中实现的收件人列表。对于每封电子邮件,发件人可以指定收件人列表。然后,邮件系统确保将消息内容传输到每个收件人。企业集成领域的一个例子是一种功能可以由一个或多个提供商执行的情况。例如,我们可能与多个信用机构签订合同来评估客户的信用度。当收到小订单时,我们可以简单地将信用请求消息发送给一家信用机构。如果客户下了大订单,我们可能希望将信用请求消息发送给多个机构并在做出决定之前比较结果。在这种情况下,收件人列表取决于订单的美元价值。
In some cases, though, we may want to specify one or more recipients for the message. A common analogy is the recipient lists implemented in most e-mail systems. For each e-mail message, the sender can specify a list of recipients. The mail system then ensures transport of the message content to each recipient. An example from the domain of enterprise integration would be a situation where a function can be performed by one or more providers. For example, we may have a contract with multiple credit agencies to assess the credit worthiness of our customers. When a small order comes in, we may simply route the credit request message to one credit agency. If a customer places a large order, we may want to route the credit request message to multiple agencies and compare the results before making a decision. In this case, the list of recipients depends on the dollar value of the order.
在另一种情况下,我们可能希望将订单消息路由到选定的供应商列表,以获得所请求项目的报价。我们可能希望根据用户偏好来控制哪些供应商接收请求,而不是将请求发送给所有供应商。
In another situation, we may want to route an order message to a select list of suppliers to obtain a quote for a requested item. Rather than sending the request to all vendors, we may want to control which vendors receive the request, possibly based on user preferences.
|
我们如何将消息路由到动态收件人列表? How do we route a message to a dynamic list of recipients? |
这个问题是基于内容的路由器解决的问题的扩展,因此该模式中描述的一些相同的力量和替代方案也在这里发挥作用。
This problem is an extension to the issue that a Content-Based Router solves, so some of the same forces and alternatives described in that pattern come into play here as well.
大多数消息传递系统都提供发布-订阅通道,这是一种将每条已发布消息的副本发送给订阅该通道的每个收件人的通道。收件人组基于对特定频道或主题的订阅。然而,频道的活动订阅者列表在某种程度上是静态的,并且不能逐条消息地进行控制。我们需要的是类似发布-订阅通道的东西,它可以将每条消息发送到不同的订阅者列表。这很困难,因为订阅发布-订阅频道是二进制的,您要么订阅频道上的所有消息,要么不订阅。
Most messaging systems provide Publish-Subscribe Channels, a type of channel that sends a copy of each published message to each recipient that subscribes to the channel. The set of recipients is based on subscription to the specific channel or subject. However, the list of active subscribers to a channel is somewhat static and cannot be controlled on a message-by-message basis. What we need is something like a Publish-Subscribe Channel that can send each message to a different list of subscribers. This is difficult because subscribing to a Publish-Subscribe Channel is binaryyou are either subscribed to all messages on the channel or none.
每个潜在的收件人都可以根据消息内容过滤传入的消息,最有可能使用消息过滤器或选择性消费者。不幸的是,该解决方案将谁接收消息的逻辑分发给各个订阅者,这可能会成为维护负担。为了保留中心控制点,消息可以在附加到消息的列表中指定其预期接收者。然后,当消息被广播给所有可能的接收者时,不在该消息的接收者列表中的每个接收者将丢弃该消息。
Each potential recipient could filter incoming messages based on message content, most likely using a Message Filter or Selective Consumer. Unfortunately, this solution distributes the logic of who receives the message to the individual subscribers, which could become a maintenance burden. To retain a central point of control, the message could specify its intended recipients in a list attached to the message. Then, when the message is broadcast to all possible recipients, each recipient that is not in the message's recipient list would discard the message.
这些方法的问题在于效率低下:每个潜在接收者都必须处理每条消息,即使在许多情况下接收者会决定丢弃该消息。该配置还依赖于接收者的某种“荣誉系统”,因为接收者可以决定处理它不应该处理的消息。在消息应该对某些接收者隐藏的情况下,这绝对是不可取的,例如,当将报价请求转发给选定的供应商子集并期望其他人在收到请求时忽略该请求时。
The problem with these approaches is their inefficiency: Each potential recipient must process every message, even though in many cases the recipient will decide to discard the message. The configuration also relies on a certain "honor system" on the part of the recipients, as a recipient could decide to process a message it is not supposed to process. This is definitely not desirable in situations where a message should be kept hidden from certain recipients, for example when forwarding a request for a quote to a select subset of suppliers and expecting the others to ignore the request when they receive it.
我们还可以要求消息发起者将消息单独发布给每个所需的接收者。但在这种情况下,我们会将向所有收件人传递的责任交给消息发起者。如果发起者是打包应用程序,那么这通常不是一个选项。此外,它将决策逻辑嵌入到应用程序中,这将使应用程序与集成基础设施更紧密地耦合。在许多情况下,正在集成的应用程序甚至不知道它们参与了集成解决方案,因此期望应用程序包含消息路由逻辑是不现实的。
We could also require the message originator to publish the message individually to each desired recipient. In that case, though, we would place the burden of delivery to all recipients on the message originator. If the originator is a packaged application, this is generally not an option. Also, it would embed decision logic inside the application, which would couple the application more tightly to the integration infrastructure. In many cases, the applications that are being integrated are unaware that they even participate in an integration solution, so expecting the application to contain message routing logic is not realistic.
|
为每个接收者定义一个渠道。然后使用收件人列表检查传入消息,确定所需收件人的列表,并将消息转发到与列表中的收件人关联的所有通道。 Define a channel for each recipient. Then use a Recipient List to inspect an incoming message, determine the list of desired recipients, and forward the message to all channels associated with the recipients in the list. |
尽管实现通常耦合在一起,但嵌入在收件人列表中的逻辑可以被描绘为两个独立的部分。第一部分计算收件人列表。第二部分只是遍历列表并将收到的消息的副本发送给每个收件人。就像基于内容的路由器一样,收件人列表通常不会修改消息内容。
The logic embedded in a Recipient List can be pictured as two separate parts even though the implementation is often coupled together. The first part computes a list of recipients. The second part simply traverses the list and sends a copy of the received message to each recipient. Just like a Content-Based Router, the Recipient List usually does not modify the message contents.
收件人列表可以计算收件人(左)或让另一个组件提供列表(右)
A Recipient List Can Compute the Recipients (left) or Have Another Component Provide a List (right)
接收者列表可以从多个来源获得。列表的创建可以在收件人列表外部,以便消息发起者或另一个组件将该列表附加到传入消息。在这种情况下,收件人列表只需迭代这个现成的列表。收件人列表通常会从邮件中删除列表,以减少传出邮件的大小并防止个别收件人看到列表中的其他人。或者,如果每条消息的目的地由外部因素(例如用户选择)驱动,则向传入消息提供收件人列表是有意义的。
The list of recipients can be derived from a number of sources. The creation of the list can be external to the Recipient List so that the message originator or another component attaches the list to the incoming message. In that case, the Recipient List only has to iterate through this ready-made list. The Recipient List usually removes the list from the message to reduce the size of the outgoing messages and prevent individual recipients from seeing who else is on the list. Alternatively, providing the list of recipients with the incoming message makes sense if the destinations of each message are driven by external factors, such as user selection.
在大多数情况下,收件人列表根据邮件内容和收件人列表中嵌入的一组规则来计算收件人列表。这些规则可以是硬编码的或可配置的(参见下页)。
In most cases, the Recipient List computes the list of recipients based on the content of the message and a set of rules embedded in the Recipient List. These rules may be hard-coded or configurable (see the following page).
收件人列表受到与消息路由器中讨论的有关耦合的相同注意事项的约束。将消息预测性地路由到各个接收者可能会导致组件之间的耦合更紧密,因为中央组件必须了解一系列其他组件。
The Recipient List is subject to the same considerations regarding coupling as discussed in Message Router. Routing messages predictively to individual recipients can lead to tighter coupling between components because a central component has to have knowledge of a series of other components.
为了让收件人列表控制信息流,我们需要确保收件人不能直接订阅到收件人列表的输入通道,从而绕过收件人列表执行的任何控制。
In order for the Recipient List to control flow of information, we need to make sure that recipients cannot subscribe directly to the input channel into the Recipient List, bypassing any control the Recipient List exercises.
收件人列表组件负责将传入消息发送到收件人列表中指定的每个收件人。收件人列表的稳健实现必须能够处理传入消息,但仅在所有出站消息成功发送后才“使用”它。因此,接收者列表组件必须确保接收一条消息和发送多条消息的完整操作是原子的。如果收件人列表失败,它必须是可重新启动的,这意味着它必须能够完成组件失败时正在进行的任何操作。这可以通过多种方式完成:
The Recipient List component is responsible for sending the incoming message to each recipient specified in the recipient list. A robust implementation of the Recipient List must be able to process the incoming message but only "consume" it after all outbound messages have been successfully sent. As such, the Recipient List component has to ensure that the complete operation of receiving one message and sending multiple messages is atomic. If a Recipient List fails, it must be restartable, meaning it must be able to complete any operation that was in progress when the component failed. This can be accomplished in multiple ways:
单个事务: 收件人列表可以使用事务通道并将消息作为单个事务的一部分放置在出站通道上。在所有消息都放置在通道上之前,它不会提交消息。这保证了要么发送所有消息,要么不发送消息。
Single transaction: The Recipient List can use transactional channels and place the message on the outbound channels as part of a single transaction. It does not commit the messages until all messages are placed on the channels. This guarantees that either all or no messages are sent.
持久收件人列表: 收件人列表可以“记住”它已经发送了哪些消息,以便在失败和重新启动时可以将消息发送给剩余的收件人。收件人列表可以存储在磁盘或数据库上,以便在收件人列表组件崩溃时仍能幸存。
Persistent recipient list: The Recipient List can "remember" which messages it already sent so that on failure and restart it can send messages to the remaining recipients. The recipient list could be stored on disk or a database so that it survives a crash of the Recipient List component.
幂等接收者: 或者,接收者列表可以简单地在重新启动时重新发送所有消息。此选项要求所有潜在接收者都是幂等的(请参阅幂等接收者) 。幂等函数是指那些应用到自身上不会改变系统状态的函数;也就是说,如果同一条消息被处理两次,组件的状态不会受到影响。消息本质上可以是幂等的(例如,消息“所有小部件在 5 月 30 日之前促销”或“给我获取 XYZ 小部件的报价”如果收到两次,则不太可能造成损害),或者可以通过以下方式使接收组件成为幂等的:插入特殊的消息过滤器这消除了重复的消息。幂等性非常方便,因为当我们怀疑收件人是否已收到消息时,它允许我们简单地重新发送消息。TCP/IP 协议使用类似的机制来确保可靠的消息传递,而无需不必要的开销(请参阅 [ Stevens ])。
Idempotent receivers: Alternatively, the Recipient List could simply resend all messages on restart. This option requires all potential recipients to be idempotent (see Idempotent Receiver ). Idempotent functions are those that do not change the state of the system if they are applied to themselves; that is, the state of the component is not affected if the same message is processed twice. Messages can be inherently idempotent (e.g., the messages "All Widgets on Sale until May 30" or "Get me a quote for XYZ widgets" are unlikely to do harm if they are received twice), or the receiving component can be made idempotent by inserting a special Message Filter that eliminates duplicate messages. Idempotence is very handy because it allows us to simply resend messages when we are in doubt whether the recipient has received it. The TCP/IP protocol uses a similar mechanism to ensure reliable message delivery without unnecessary overhead (see [Stevens]).
尽管收件人列表的目的是保持控制,但让收件人自己配置存储在收件人列表中的规则可能会很有用,例如,如果收件人想要根据无法轻松表示的规则订阅特定消息,发布-订阅频道主题的形式。我们在消息过滤器下提到了这些类型的订阅规则模式例如“如果价格低于 48.31 美元,则接受消息。” 为了最大限度地减少网络流量,我们仍然希望仅将消息发送给感兴趣的各方,而不是广播消息并让每个接收者决定是否处理消息。为了实现此功能,接收者可以通过特殊的控制通道将其订阅首选项发送到接收者列表。收件人列表将首选项存储在规则库中,并使用它来编译每条消息的收件人列表。这种方法使订阅者可以控制消息过滤,但可以利用收件人列表的效率来分发消息。该解决方案将动态路由器的属性与收件人列表相结合,以创建动态收件人列表(见图)。
Even though the intent of the Recipient List is to maintain control, it can be useful to let the recipients themselves configure the rules stored in the Recipient Listfor example, if recipients want to subscribe to specific messages based on rules that cannot easily be represented in the form of Publish-Subscribe Channel topics. We mentioned these types of subscription rules under the Message Filter patternfor example "accept the message if the price is less than $48.31." To minimize network traffic, we would still want to send the messages only to interested parties as opposed to broadcasting it and letting each recipient decide whether or not to process the message. To implement this functionality, recipients can send their subscription preferences to the Recipient List via a special control channel. The Recipient List stores the preferences in a rules base and uses it to compile the recipient list for each message. This approach gives the subscribers control over the message filtering but leverages the efficiency of the Recipient List to distribute the messages. This solution combines the properties of a Dynamic Router with a Recipient List to create a dynamic Recipient List (see figure).
动态收件人列表由收件人通过控制通道配置
A Dynamic Recipient List Is Configured by the Recipients via a Control Channel
这种方法对于消息过滤器模式中讨论的价格更新示例非常有效。由于它将控制权分配给各个接收者,因此它不适合此模式开头提到的报价示例,因为我们希望控制参与投标的供应商。
This approach would work well for the price update example discussed in the Message Filter pattern. Since it assigns control to the individual recipients, it is not suitable for the price quote example mentioned at the beginning of this pattern, though, because we want to control the vendors that get to participate in the bid.
将一条消息发送给所有可能的收件人(然后由其过滤该消息)或将单独的消息发送给每个收件人是否更有效,这在很大程度上取决于消息传递基础设施的实现。一般来说,我们可以假设消息的收件人越多,它引起的网络流量就越多。不过,也有例外。一些发布-订阅消息传递系统基于 IP 多播功能,可以通过单次网络传输将消息路由到多个收件人(仅需要对丢失的消息进行重传)。IP 多播利用以太网的总线架构。当 IP 数据包通过网络发送时,同一以太网段上的所有网络适配器 (NIC) 都会接收该数据包。通常情况下,NIC 验证数据包的预期接收者,如果数据包未发送至与 NIC 关联的 IP 地址,则忽略它。多播路由允许属于指定多播组的所有接收者从总线上读取数据包。这导致单个数据包能够被多个 NIC 接收,然后这些 NIC 将数据传递到与网络连接关联的相应应用程序。由于以太网总线架构,这种方法在本地网络上非常有效。它无法在需要点对点 TCP/IP 连接的 Internet 上工作。一般来说,我们可以说接收者之间的距离越远,使用电子邮件的效率就越高。多播路由允许属于指定多播组的所有接收者从总线上读取数据包。这导致单个数据包能够被多个 NIC 接收,然后这些 NIC 将数据传递到与网络连接关联的相应应用程序。由于以太网总线架构,这种方法在本地网络上非常有效。它无法在需要点对点 TCP/IP 连接的 Internet 上工作。一般来说,我们可以说接收者之间的距离越远,使用电子邮件的效率就越高。多播路由允许属于指定多播组的所有接收者从总线上读取数据包。这导致单个数据包能够被多个 NIC 接收,然后这些 NIC 将数据传递到与网络连接关联的相应应用程序。由于以太网总线架构,这种方法在本地网络上非常有效。它无法在需要点对点 TCP/IP 连接的 Internet 上工作。一般来说,我们可以说接收者之间的距离越远,使用电子邮件的效率就越高。这导致单个数据包能够被多个 NIC 接收,然后这些 NIC 将数据传递到与网络连接关联的相应应用程序。由于以太网总线架构,这种方法在本地网络上非常有效。它无法在需要点对点 TCP/IP 连接的 Internet 上工作。一般来说,我们可以说接收者之间的距离越远,使用电子邮件的效率就越高。这导致单个数据包能够被多个 NIC 接收,然后这些 NIC 将数据传递到与网络连接关联的相应应用程序。由于以太网总线架构,这种方法在本地网络上非常有效。它无法在需要点对点 TCP/IP 连接的 Internet 上工作。一般来说,我们可以说接收者之间的距离越远,使用电子邮件的效率就越高。收件人列表而不是发布-订阅通道。
Whether it is more efficient to send one message to all possible recipients who then filter the message or to send individual messages to each recipient depends very much on the implementation of the messaging infrastructure. Generally, we can assume that the more recipients a message has, the more network traffic it causes. However, there are exceptions. Some publish-subscribe messaging systems are based on IP Multicast functionality and can route messages to multiple recipients with a single network transmission (requiring retransmission only for lost messages). IP Multicast takes advantage of Ethernet's bus architecture. When an IP packet is sent across the network, all network adapters (NIC) on the same Ethernet segment receive the packet. Normally, the NIC verifies the intended recipient of the packet and ignores it if the packet is not addressed to the IP address the NIC is associated with. Multicast routing allows all receivers that are part of a specified multicast group to read the packet off the bus. This results in a single packet being able to be received by multiple NICs who then pass the data to the respective application associated with the network connection. This approach can be very efficient on local networks due to the Ethernet bus architecture. It does not work across the Internet where point-to-point TCP/IP connections are required. In general, we can say that the further apart the recipients are, the more efficient it is to use a Recipient List instead of a Publish-Subscribe Channel.
广播方法是否比收件人列表更有效不仅取决于网络基础设施,还取决于收件人总数与应处理消息的数量之间的比例。如果平均而言大多数收件人都在收件人列表中,则简单地广播消息并让(少数)非参与者过滤掉该消息可能会更有效。然而,如果平均而言,所有可能的收件人中只有一小部分对特定消息感兴趣,则几乎可以保证收件人列表会更有效。
Whether a broadcast approach is more efficient than a Recipient List depends not only on the network infrastructure, but also on the proportion between the total number of recipients and the number that are supposed to process the message. If on average most recipients are in the recipient list, it may be more efficient to simply broadcast the message and have the (few) nonparticipants filter the message out. If, however, on average only a small portion of all possible recipients are interested in a particular message, the Recipient List is almost guaranteed to be more efficient.
我们多次对比了使用带有收件人列表的预测路由和使用发布-订阅通道和消息过滤器数组的反应式过滤来实现相同的功能。一些决策标准与基于内容的路由器和消息过滤器阵列之间的比较的标准相同。但是,如果是收件人列表,则邮件可以发送给多个收件人,从而使过滤器选项更具吸引力。
A number of times we have contrasted implementing the same functionality using predictive routing with a Recipient List and using reactive filtering using a Publish-Subscribe Channel and an array of Message Filters. Some of the decision criteria are the same as those of the comparison between the Content-Based Router and the Message Filter array. However, in case of a Recipient List, the message can travel to multiple recipients, making the filter option more attractive.
收件人列表与消息过滤器数组
Recipient List versus Message Filter Array
下表对两种解决方案进行了比较:
The following table compares the two solutions:
收件人名单 Recipient List | 带有消息过滤器的发布-订阅通道 Publish-Subscribe Channel with Message Filters |
|---|---|
中央控制和维护预测路由。 Central control and maintenancepredictive routing. | 分布式控制和维护无功滤波。 Distributed control and maintenancereactive filtering. |
路由器需要了解参与者。如果添加或删除参与者,则可能需要更新路由器(除非使用动态路由器,但代价是失去控制)。 Router needs to know about participants. Router may need to be updated if participants are added or removed (unless using dynamic router, but at expense of losing control). | 无需参与者了解。添加或删除参与者很容易。 No knowledge of participants required. Adding or removing participants is easy. |
通常用于商业交易,例如请求报价。 Often used for business transactions, e.g., request for quote. | 通常用于事件通知或信息性消息。 Often used for event notifications or informational messages. |
如果仅限于基于队列的通道,通常会更有效。 Generally more efficient if limited to queue-based channels. | 通过发布-订阅通道可以提高效率(取决于基础设施)。 Can be more efficient with publish-subscribe channels (depends on infrastructure). |
如果我们向多个收件人发送消息,我们可能需要稍后协调结果。例如,如果我们向多个信用机构发送信用评分请求,我们应该等到所有结果返回,以便我们可以比较结果并准确计算信用评分中位数。对于其他不太重要的功能,我们可能只采用第一个可用的响应来优化消息吞吐量。这些类型的策略通常在聚合器内实现。分散-聚集描述了我们从一条消息开始,将其发送给多个收件人,然后将响应重新组合回一条消息的情况。
If we send a message to multiple recipients, we may need to reconcile the results later. For example, if we send a request for a credit score to multiple credit agencies, we should wait until all results come back so that we can compare the results and accurately compute the median credit score. With other, less critical functions, we may just take the first available response to optimize message throughput. These types of strategies are typically implemented inside an Aggregator. Scatter-Gather describes situations in which we start with a single message, send it to multiple recipients, and recombine the responses back into a single message.
如果消息传递系统仅提供点对点通道但不提供发布-订阅通道,则动态收件人列表可用于实现发布-订阅通道。接收者列表将保留订阅该主题的所有点对点频道的列表。每个主题都可以由一个特定的收件人列表实例来表示。如果我们需要应用特殊标准以允许收件人订阅特定数据源,则此解决方案也很有用。虽然大多数发布-订阅通道允许任何组件订阅该通道,但接收者列表通过限制谁可以订阅列表,可以轻松实现逻辑来控制对源数据的访问。当然,这假设消息传递系统阻止接收者直接访问接收者列表的输入通道。
A dynamic Recipient List can be used to implement a Publish-Subscribe Channel if a messaging system provides only Point-to-Point Channels but no Publish-Subscribe Channels. The Recipient List would keep a list of all Point-to-Point Channels that are subscribed to the topic. Each topic can be represented by one specific Recipient List instance. This solution can also be useful if we need to apply special criteria to allow recipients to subscribe to a specific data source. While most Publish-Subscribe Channels allow any component to subscribe to the channel, the Recipient List could easily implement logic to control access to the source data by limiting who gets to subscribe to the list. Of course, this assumes that the messaging system prevents the recipients from directly accessing the input channel into the Recipient List.
|
示例: 贷款经纪人 Example: Loan Broker 第 9 章“插曲:组合消息传递”中的组合消息传递示例使用收件人列表将贷款报价请求仅发送给合格的银行。插曲显示了Java、C# 和 TIBCO 中收件人列表的实现。 The composed messaging example in Chapter 9, "Interlude: Composed Messaging," uses a Recipient List to route a loan quote request only to qualified banks. The interlude shows implementations of the Recipient List in Java, C#, and TIBCO. |
|
示例: C# 和 MSMQ 中的动态收件人列表 Example: Dynamic Recipient List in C# and MSMQ 此示例以动态路由器示例为基础,将其转换为动态收件人列表。代码结构非常相似。DynamicRecipientList侦听两个输入队列,一个用于传入消息 ( inQueue ),另一个用于控制队列 ( controlQueue),接收者可以在其中提交他们的订阅偏好。控制队列上的消息必须格式化为由冒号 (:) 分隔的两部分组成的字符串。第一部分是指示接收者订阅首选项的字符列表。收件人表示希望接收以指定字母之一开头的所有消息。控制消息的第二部分指定接收方侦听的队列的名称。例如,控制消息W:WidgetQueue告诉DynamicRecipientList 将所有以 W 开头的传入消息路由到队列 WidgetQueue 。 同样,消息WG:WidgetGadgetQueue指示DynamicRecipientList 将以 W 或 G 开头的消息到队列WidgetGadgetQueue 。 This example builds on the Dynamic Router example to turn it into a dynamic Recipient List. The code structure is very similar. The DynamicRecipientList listens on two input queues, one for incoming messages (inQueue) and a control queue (controlQueue) where recipients can hand in their subscription preferences. Messages on the control queue have to be formatted as a string consisting of two parts separated by a colon (:). The first part is a list of characters that indicate the subscription preference of the recipient. The recipient expresses that it wants to receive all messages starting with one of the specified letters. The second part of the control message specifies the name of the queue that the recipient listens on. For example, the control message W:WidgetQueue tells the DynamicRecipientList to route all incoming messages that begin with W to the queue WidgetQueue. Likewise, the message WG:WidgetGadgetQueue instructs the DynamicRecipientList to route messages that start with either W or G to the queue WidgetGadgetQueue. 动态收件人列表类 { 受保护的消息队列inQueue; 受保护的消息队列控制队列; 受保护的 IDictionary 路由表 = (IDictionary)(new class DynamicRecipientList { protected MessageQueue inQueue; protected MessageQueue controlQueue; protected IDictionary routingTable = (IDictionary)(new Hashtable()); public DynamicRecipientList(MessageQueue inQueue, MessageQueue controlQueue) { this.inQueue = inQueue; this.controlQueue = controlQueue; inQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnMessage); inQueue.BeginReceive(); controlQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnControlMessage); controlQueue.BeginReceive(); } protected void OnMessage(Object source, ReceiveCompletedEventArgs asyncResult) { MessageQueue mq = (MessageQueue)source; mq.Formatter = new System.Messaging.XmlMessageFormatter (new String[] {"System.String,mscorlib"}); Message message = mq.EndReceive(asyncResult.AsyncResult); if (((String)message.Body).Length > 0) { char key = ((String)message.Body)[0]; ArrayList destinations = (ArrayList)routingTable[key]; foreach (MessageQueue destination in destinations) { destination.Send(message); Console.WriteLine("sending message " + message.Body + " to " + destination.Path); } } mq.BeginReceive(); } // control message format is XYZ:QueueName as a single string protected void OnControlMessage(Object source, ReceiveCompletedEventArgs asyncResult) { MessageQueue mq = (MessageQueue)source; mq.Formatter = new System.Messaging.XmlMessageFormatter (new String[] {"System.String,mscorlib"}); Message message = mq.EndReceive(asyncResult.AsyncResult); String text = ((String)message.Body); String [] split = (text.Split(new char[] {':'}, 2)); if (split.Length == 2) { char[] keys = split[0].ToCharArray(); String queueName = split[1]; MessageQueue queue = FindQueue(queueName); foreach (char c in keys) { if (!routingTable.Contains(c)) { routingTable.Add(c, new ArrayList()); } ((ArrayList)(routingTable[c])).Add(queue); Console.WriteLine("Subscribed queue " + queueName + " for message " + c); } } mq.BeginReceive(); } protected MessageQueue FindQueue(string queueName) { if (!MessageQueue.Exists(queueName)) { return MessageQueue.Create(queueName); } else return new MessageQueue(queueName); } } DynamicRecipientList使用更聪明(读起来复杂)的方式来存储收件人的首选项。为了优化传入消息的处理,DynamicRecipientList维护一个以传入消息的第一个字母为键的哈希表。与动态路由器示例不同,哈希表不包含单个目标,而是包含所有订阅目标的 ArrayList。当DynamicRecipientList 收到消息时,它从哈希表中找到正确的目标列表然后迭代列表以向每个目的地发送一条消息。 The DynamicRecipientList uses a bit more clever (read complicated) way to store the recipient's preferences. To optimize processing of incoming messages, the DynamicRecipientList maintains a Hashtable keyed by the first letter of incoming messages. Unlike in the Dynamic Router example, the Hashtable contains not a single destination, but an ArrayList of all subscribed destinations. When the DynamicRecipientList receives a message, it locates the correct destination list from the Hashtable and then iterates over the list to send one message to each destination. 此示例不使用dunnoChannel(请参阅基于动态内容路由器)来处理与任何条件不匹配的传入消息。通常,如果邮件的收件人数为零,则收件人列表不会将其视为错误。 This example does not use a dunnoChannel (see Content-Based Router or Dynamic Router ) for incoming messages that do not match any criteria. Typically, a Recipient List does not consider it an error if there are zero recipients for a message. 此实现不允许收件人取消订阅。它也不会检测重复订阅。例如,如果收件人订阅同一消息类型两次,它将收到重复的消息。这与典型的发布-订阅语义不同,在典型的发布-订阅语义中,特定接收者只能订阅一个频道一次。如果需要,可以轻松更改以禁止重复订阅。 This implementation does not allow recipients to unsubscribe. It also does not detect duplicate subscriptions. For example, if a recipient subscribes twice for the same message type, it will receive duplicate messages. This is different from the typical publish-subscribe semantics where a specific recipient can subscribe to one channel only once. The DynamicRecipientList could easily be changed to disallow duplicate subscriptions if that is desired. |
通过集成解决方案传递的许多消息由多个元素组成。例如,客户下的订单通常不仅仅包含单个行项目。正如基于内容的路由器的描述中所述,每个行项目可能需要由不同的库存系统处理。因此,我们需要找到一种方法来处理完整的订单,但单独处理订单中包含的每个订单项。
Many messages passing through an integration solution consist of multiple elements. For example, an order placed by a customer typically consists of more than just a single line item. As outlined in the description of the Content-Based Router, each line item may need to be handled by a different inventory system. Thus, we need to find an approach to process a complete order but treat each order item contained in the order individually.
|
如果消息包含多个元素,并且每个元素可能必须以不同的方式处理,我们如何处理它? How can we process a message if it contains multiple elements, each of which may have to be processed in a different way? |
此路由问题的解决方案应该足够通用,能够处理不同数量和类型的元素。例如,订单可以包含任意数量的商品,因此我们不想创建一个假设商品数量固定的解决方案。我们也不想对消息包含的项目类型做出太多假设。例如,如果 Widgets & Gadgets 'R Us 明天开始销售书籍,我们希望尽量减少对整体解决方案的影响。
The solution to this routing problem should be generic enough that it can deal with varying numbers and types of elements. For example, an order can contain any number of items, so we would not want to create a solution that assumes a fixed number of items. Nor would we want to make too many assumptions about what type of items the message contains. For example, if Widgets & Gadgets 'R Us starts selling books tomorrow, we want to minimize the impact on the overall solution.
我们还希望保持对订单项目的控制,避免重复或丢失处理。例如,我们可以使用发布-订阅通道将完整的订单发送到每个订单管理系统,并让它挑选出它可以处理的项目。这种方法具有与基于内容的路由器中描述的相同的缺点;很难避免个别物品的丢失或重复运输。
We also want to maintain control over the order items and avoid duplicated or lost processing. For example, we could send the complete order to each order management system using a Publish-Subscribe Channel and let it pick out the items that it can handle. This approach has the same disadvantages described in the Content-Based Router; it would be very difficult to avoid losing or duplicating shipments of individual items.
该解决方案还应该有效地利用网络资源。将完整的订单消息发送到可能仅处理订单的一部分的每个系统可能会导致额外的消息流量,尤其是随着目的地数量的增加。
The solution should also be efficient in its use of network resources. Sending the complete order message to each system that may process only a portion of the order can cause additional message traffic, especially as the number of destinations increases.
为了避免多次发送完整的消息,我们可以将原始消息拆分为与库存系统一样多的消息。然后,每条消息将仅包含可由特定系统处理的行项目。这种方法类似于基于内容的路由器,只不过我们分割消息,然后路由各个消息。这种方法很有效,但将解决方案与有关特定项目类型和相关目的地的知识联系起来。如果我们想改变路由规则怎么办?我们现在必须更改这个更复杂的“项目路由器”组件。我们使用了管道和过滤器架构之前将处理分解为定义良好的、可组合的组件,而不是将多个功能集中在一起,因此我们也应该能够在这里利用这种架构。
To avoid sending the complete message multiple times, we could split the original message into as many messages as there are inventory systems. Each message would then contain only the line items that can be handled by the specific system. This approach is similar to a Content-Based Router except we are splitting the message and then routing the individual messages. This approach would be efficient but ties the solution to knowledge about the specific item types and associated destinations. What if we want to change the routing rules? We would now have to change this more complex "item router" component. We used the Pipes and Filters architecture before to break out processing into well-defined, composable components as opposed to lumping multiple functions together, so we should be able to take advantage of this architecture here as well.
|
使用拆分器将复合消息分解为一系列单独的消息,每条消息都包含与一个项目相关的数据。 Use a Splitter to break out the composite message into a series of individual messages, each containing data related to one item. |
拆分器为传入消息中的每个单个元素(或元素子集)发布一条消息。在许多情况下,我们希望在每条结果消息中重复一些常见元素。可能需要这些额外的元素来使生成的子消息自包含并启用每个子消息的无状态处理。它还允许稍后协调相关的子消息。例如,每个订单项消息应包含订单号的副本,以便我们可以正确地将订单项关联回订单以及所有关联实体,例如下订单的客户(见图)。
The Splitter publishes one message for each single element (or a subset of elements) in the incoming message. In many cases, we want to repeat some common elements in each resulting message. These extra elements may be required to make the resulting child message self-contained and enable stateless processing of each child message. It also allows reconciliation of associated child messages later on. For example, each order item message should contain a copy of the order number so we can properly associate the order item back to the order and all associated entities such as the customer placing the order (see figure).
将公共数据元素复制到每个子消息中
Copying a Common Data Element into Each Child Message
如前所述,许多企业集成系统以树形结构存储消息数据。树结构的美妙之处在于它是递归的。一个节点下面的每个子节点都是另一个子树的根。这使我们能够提取消息树的各个部分,并将它们作为消息树进一步处理。如果我们使用消息树,则可以轻松地将 Splitter配置为迭代指定节点下的所有子节点,并为每个子节点发送一条消息。这样的Splitter实现将是完全通用的,因为它不对子元素的数量和类型做出任何假设。许多商业 EAI 工具在术语迭代器或定序器。由于我们试图避免供应商词汇以减少混淆的可能性,因此我们将这种样式的Splitter称为迭代Splitter 。
As mentioned earlier, many enterprise integration systems store message data in a tree structure. The beauty of a tree structure is that it is recursive. Each child node underneath a node is the root of another subtree. This allows us to extract pieces of a message tree and process them further as a message tree on their own. If we use message trees, the Splitter can easily be configured to iterate through all children under a specified node and send one message for each child node. Such a Splitter implementation would be completely generic because it does not make any assumptions about the number and type of child elements. Many commercial EAI tools provide this type of functionality under the term iterator or sequencer. Since we are trying to avoid vendor vocabulary to reduce potential for confusion, we call this style of Splitter an iterating Splitter.
使用分离器但不限于重复元素。可以将大消息分割成单独的消息以简化处理。例如,许多 B2B 信息交换标准指定了非常全面的消息格式。这些巨大的消息通常是委员会设计的结果,并且大部分消息可能很少被使用。在许多情况下,将这些大型消息分割成单独的消息是有帮助的,每个消息都集中在大型消息的特定部分。这使得后续转换的开发变得更加容易,并且还可以节省网络带宽,因为我们可以将较小的消息路由到那些仅处理大型消息的一部分的组件。生成的消息通常发布到不同的通道而不是同一通道,因为它们代表不同子类型的消息。在这种情况下,结果消息的数量通常是固定的,而更一般的Splitter假定项目数量可变。为了区分这种风格的Splitter,我们将其称为静态Splitter 。静态拆分器在功能上等同于使用广播频道,后跟一组内容过滤器。
Using a Splitter is not limited to repeating elements, though. A large message may be split into individual messages to simplify processing. For example, a number of B2B information-exchange standards specify very comprehensive message formats. These huge messages are often a result of design-by-committee, and large portions of the messages may rarely be used. In many instances it is helpful to split these mega-messages into individual messages, each centered on a specific portion of the large message. This makes subsequent transformations much easier to develop and can also save network bandwidth, since we can route smaller messages to those components that deal only with a portion of the mega-message. The resulting messages are often published to different channels rather than to the same channel because they represent messages of different subtypes. In this scenario, the number of resulting messages is generally fixed, whereas the more general Splitter assumes a variable number of items. To distinguish this style of Splitter, we call it static Splitter. A static Splitter is functionally equivalent to using a broadcast channel followed by a set of Content Filters.
静态拆分器将大消息分解为固定数量的较小消息
A Static Splitter Breaks Up a Large Message into a Fixed Number of Smaller Messages
在某些情况下,为子消息配备序列号以提高消息可追溯性并简化聚合器的任务很有用。 此外,最好为每个消息配备对原始(组合)消息的引用,以便可以将各个消息的处理结果关联回原始消息。该引用充当相关标识符。
In some cases it is useful to equip child messages with sequence numbers to improve message traceability and simplify the task of an Aggregator. Also, it is a good idea to equip each message with a reference to the original (combined) message so that processing results from the individual messages can be correlated back to the original message. This reference acts as a Correlation Identifier.
如果使用消息信封(请参阅信封包装器) ,则应为每个新消息提供自己的消息信封,以使其符合消息传递基础结构。例如,如果基础设施要求消息在消息头中携带时间戳,我们会将原始消息的时间戳传播到每个消息的头。
If message envelopes are used (see Envelope Wrapper ), each new message should be supplied with its own message envelope to make it compliant with the messaging infrastructure. For example, if the infrastructure requires a message to carry a timestamp in the message header, we would propagate the timestamp of the original message to each message's header.
|
示例: 在 C# 中拆分 XML 订单文档 Example: Splitting an XML Order Document in C# 许多消息传递系统使用 XML 消息。例如,假设收到的订单如下所示: Many messaging systems use XML messages. For example, let's assume an incoming order looks as follows: <订单>
<日期>2002年7月18日</日期>
<订单号>3825968</订单号>
<顾客>
<id>12345</id>
<名字>乔·多伊</名字>
</客户>
<订单项目>
<项目>
<数量>3.0</数量>
<项目编号>W1234</项目编号>
<描述>小部件</描述>
</项目>
<项目>
<数量>2.0</数量>
<项目编号>G2345</项目编号>
<描述>小工具</描述>
</项目>
</订单项目>
</订单>
<order>
<date>7/18/2002</date>
<ordernumber>3825968</ordernumber>
<customer>
<id>12345</id>
<name>Joe Doe</name>
</customer>
<orderitems>
<item>
<quantity>3.0</quantity>
<itemno>W1234</itemno>
<description>A Widget</description>
</item>
<item>
<quantity>2.0</quantity>
<itemno>G2345</itemno>
<description>A Gadget</description>
</item>
</orderitems>
</order>
我们希望拆分器将订单拆分为单独的订单项目。对于示例文档,Splitter应生成以下两条消息: We want the Splitter to split the order into individual order items. For the example document, the Splitter should generate the following two messages: <订单项目>
<日期>2002年7月18日</日期>
<订单号>3825968</订单号>
<客户ID>12345</客户ID>
<数量>3.0</数量>
<项目编号>W1234</项目编号>
<描述>小部件</描述>
</订单项目>
<订单项目>
<日期>2002年7月18日</日期>
<订单号>3825968</订单号>
<客户ID>12345</客户ID>
<数量>2.0</数量>
<项目编号>G2345</项目编号>
<描述>小工具</描述>
</订单项目>
<orderitem>
<date>7/18/2002</date>
<ordernumber>3825968</ordernumber>
<customerid>12345</customerid>
<quantity>3.0</quantity>
<itemno>W1234</itemno>
<description>A Widget</description>
</orderitem>
<orderitem>
<date>7/18/2002</date>
<ordernumber>3825968</ordernumber>
<customerid>12345</customerid>
<quantity>2.0</quantity>
<itemno>G2345</itemno>
<description>A Gadget</description>
</orderitem>
每条订单项消息都使用原始订单消息中的订单日期、订单号和客户 ID 进行丰富。包含客户 ID 和订单日期使消息成为独立的,并使消息使用者无需存储各个消息的上下文。如果消息要由无状态服务器处理,这一点很重要。添加ordernumber字段对于以后重新聚合项目是必要的(请参阅聚合器) 。在此示例中,我们假设订单内商品的具体顺序与订单的完成无关,因此我们不必包含商品编号。 Each orderitem message is being enriched with the order date, the order number, and the customer ID from the original order message. The inclusion of the customer ID and the order date makes the message self-contained and keeps the message consumer from having to store context across individual messages. This is important if the messages are to be processed by stateless servers. The addition of the ordernumber field is necessary for later reaggregation of the items (see Aggregator ). In this example we assume that the specific sequence of items inside an order is not relevant for completion of the order, so we did not have to include an item number. 让我们看看C# 中的Splitter代码是什么样的。 Let's see what the Splitter code looks like in C#. XMLSplitter 类 { 受保护的消息队列inQueue; 受保护的消息队列outQueue; 公共 XMLSplitter(消息队列 inQueue, 消息队列 outQueue) { this.inQueue = inQueue; this.outQueue = outQueue; inQueue.ReceiveCompleted += 新 class XMLSplitter { protected MessageQueue inQueue; protected MessageQueue outQueue; public XMLSplitter(MessageQueue inQueue, MessageQueue outQueue) { this.inQueue = inQueue; this.outQueue = outQueue; inQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnMessage); inQueue.BeginReceive(); outQueue.Formatter = new ActiveXMessageFormatter(); } protected void OnMessage(Object source, ReceiveCompletedEventArgs asyncResult) { MessageQueue mq = (MessageQueue)source; mq.Formatter = new ActiveXMessageFormatter(); Message message = mq.EndReceive(asyncResult.AsyncResult); XmlDocument doc = new XmlDocument(); doc.LoadXml((String)message.Body); XmlNodeList nodeList; XmlElement root = doc.DocumentElement; XmlNode date = root.SelectSingleNode("date"); XmlNode ordernumber = root.SelectSingleNode("ordernumber"); XmlNode id = root.SelectSingleNode("customer/id"); XmlElement customerid = doc.CreateElement("customerid"); customerid.InnerText = id.InnerXml; nodeList = root.SelectNodes("/order/orderitems/item"); foreach (XmlNode item in nodeList) { XmlDocument orderItemDoc = new XmlDocument(); orderItemDoc.LoadXml("<orderitem/>"); XmlElement orderItem = orderItemDoc.DocumentElement; orderItem.AppendChild(orderItemDoc.ImportNode(date, true)); orderItem.AppendChild(orderItemDoc.ImportNode (ordernumber, true)); orderItem.AppendChild(orderItemDoc.ImportNode (customerid, true)); for (int i=0; i < item.ChildNodes.Count; i++) { orderItem.AppendChild(orderItemDoc.ImportNode (item.ChildNodes[i], true)); } outQueue.Send(orderItem.OuterXml); } mq.BeginReceive(); } } 大多数代码都集中在 XML 处理上。XMLSplitter使用与其他路由示例相同的事件驱动消费者结构。每个传入消息都会调用OnMessage方法,该方法将消息正文转换为 XML 文档以进行操作。首先,我们从订单文档中提取相关值。然后,我们迭代每个<item>子元素。我们通过指定 XPath 表达式/order/orderitems/item来完成此操作。简单的 XPath 表达式与文件路径非常相似,它沿着文档树向下延伸,与路径中指定的元素名称相匹配。对于每个<项目>我们组装一个新的 XML 文档,复制从订单和项目的子节点继承的字段。 Most of the code centers on the XML processing. The XMLSplitter uses the same Event-Driven Consumer structure as the other routing examples. Each incoming message invokes the method OnMessage, which converts the message body into an XML document for manipulation. First, we extract the relevant values from the order document. Then, we iterate over each <item> child element. We do this by specifying the XPath expression /order/orderitems/item. A simple XPath expression is very similar to a file pathit descends down the document tree, matching the element names specified in the path. For each <item> we assemble a new XML document, copying the fields carried over from the order and the item's child nodes. |
|
示例: 使用 C# 和 XSL 拆分 XML 订单文档 Example: Splitting an XML Order Document in C# and XSL 我们还可以创建一个 XSL 文档来将传入的 XML 转换为所需的格式,然后从转换后的 XML 文档创建输出消息,而不是手动操作 XML 节点和元素。当文档格式可能发生变化时,这更易于维护。我们所要做的就是更改 XSL 转换,而不对 C# 代码进行任何更改。 Instead of manipulating XML nodes and elements manually, we can also create an XSL document to transform the incoming XML into the desired format and then create output messages from the transformed XML document. That is more maintainable when the document format is likely to change. All we have to do is change the XSL transformation without any changes to the C# code. 新代码使用XslTransform类提供的Transform方法将输入文档转换为中间文档格式。中间文档格式对于每条结果消息都有一个子元素 orderitem 。该代码只是遍历所有子元素并为每个元素发布一条消息。 The new code uses the Transform method provided by the XslTransform class to convert the input document into an intermediate document format. The intermediate document format has one child element, orderitem, for each resulting message. The code simply traverses all child elements and publishes one message for each element. XSLSplitter 类 { 受保护的消息队列inQueue; 受保护的消息队列outQueue; protected String styleSheet = "..\\..\\Order2OrderItem.xsl"; 受保护的 XslTransform xslt; 公共 XSLSplitter(消息队列 inQueue,消息队列 outQueue) { this.inQueue = inQueue; this.outQueue = outQueue; xslt = 新的 XslTransform(); xslt.Load(styleSheet, null); outQueue.Formatter = new ActiveXMessageFormatter(); inQueue.ReceiveCompleted += 新 class XSLSplitter { protected MessageQueue inQueue; protected MessageQueue outQueue; protected String styleSheet = "..\\..\\Order2OrderItem.xsl"; protected XslTransform xslt; public XSLSplitter(MessageQueue inQueue, MessageQueue outQueue) { this.inQueue = inQueue; this.outQueue = outQueue; xslt = new XslTransform(); xslt.Load(styleSheet, null); outQueue.Formatter = new ActiveXMessageFormatter(); inQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(OnMessage); inQueue.BeginReceive(); } protected void OnMessage(Object source, ReceiveCompletedEventArgs asyncResult) { MessageQueue mq = (MessageQueue)source; mq.Formatter = new ActiveXMessageFormatter(); Message message = mq.EndReceive(asyncResult.AsyncResult); try { XPathDocument doc = new XPathDocument (new StringReader( (String)message.Body)); XmlReader reader = xslt.Transform(doc, null, new XmlUrlResolver()); XmlDocument allItems = new XmlDocument(); allItems.Load(reader); XmlNodeList nodeList = allItems.DocumentElement. GetElementsByTagName("orderitem"); foreach (XmlNode orderItem in nodeList) { outQueue.Send(orderItem.OuterXml); } } catch (Exception e) { Console.WriteLine(e.ToString()); } mq.BeginReceive(); } } 我们从单独的文件中读取 XSL 文档,以便于编辑和测试。此外,它允许我们更改Splitter 的行为,而无需重新编译代码。 We read the XSL document from a separate file to make it easier to edit and test. Also, it allows us to change the behavior of the Splitter without recompiling the code. <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999 <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999 /XSL/Transform"> <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/> <xsl:template match="/order"> <orderitems> <xsl:apply-templates select="orderitems/item"/> </orderitems> </xsl:template> <xsl:template match="item"> <orderitem> <date> <xsl:value-of select="parent::node()/parent::node ()/date"/> </date> <ordernumber> <xsl:value-of select="parent::node()/parent::node ()/ordernumber"/> </ordernumber> <customerid> <xsl:value-of select="parent::node()/parent::node ()/customer/id"/> </customerid> <xsl:apply-templates select="*"/> </orderitem> </xsl:template> <xsl:template match="*"> <xsl:copy> <xsl:apply-templates select="@* | node()"/> </xsl:copy> </xsl:template> </xsl:stylesheet> XSL 是一种声明性语言,因此它并不容易理解,除非您自己编写了相当多的 XSL(或者阅读了一本好的 XSL 书籍,例如 [Tennison] )。此 XSL 转换会查找任何出现的order元素(我们的文档中有一个)。一旦找到该元素,它就会为输出文档创建一个新的根元素(所有 XML 文档都必须有一个根元素),并继续处理输入文档的orderitems元素内的所有item元素。XSL 为找到的每个项目指定一个新的“模板”。该模板复制date、ordernumber和customerid元素order元素(即项目的父级的父级),然后附加该项目中的任何元素。生成的文档对于输入文档中的每个 item元素都有一个orderitem元素。这使得 C# 代码可以轻松地迭代元素并将它们作为消息发布。 XSL is a declarative language, so it is not easy to make sense of unless you have written a fair bit of XSL yourself (or read a good XSL book like [Tennison]). This XSL transform looks for any occurrence of the order element (there is one in our document). Once it finds this element, it creates a new root element for the output document (all XML documents have to have a single root element) and goes on to process all item elements inside the orderitems element of the input document. The XSL specifies a new "template" for each item that is found. This template copies the date, ordernumber, and customerid elements from the order element (which is the item's parent's parent) and then appends any element from the item. The resulting document has one orderitem element for each item element in the input document. This makes it easy for the C# code to iterate over the elements and publish them as messages. 我们很好奇这两个实现将如何执行。我们决定进行一次真正快速、非科学的性能测试。我们只是将 5,000 条订单消息通过管道传输到输入队列中,启动 Splitter ,并测量 10,000 条商品消息到达输出队列所需的时间。我们使用本地消息队列在一台机器上的单个程序中执行这一切。我们测量使用 DOM 提取元素的XMLSplitter需要 7 秒,基于 XSL 的Splitter需要 5.3 秒。为了建立基线,一个虚拟处理器消耗输入队列的一条消息并在输出队列上发布同一消息两次,处理 5,000 条消息花费了不到 2 秒的时间。该时间包括虚拟处理器消耗 5,000 条消息并发布 10,000 条消息,以及测试工具消耗处理器发布的 10,000 条消息。因此,看起来 XSL 操作比“手动”移动元素更有效(如果我们减去基线,XSL 大约快 35%)。我们确信这两个程序都可以调整以获得最佳性能,但看到它们并行执行很有趣。 We were curious about how the two implementations would perform. We decided to run a real quick, nonscientific performance test. We simply piped 5,000 order messages into the input queue, started the Splitter, and measured the time it took for 10,000 item messages to arrive on the output queue. We executed this all inside a single program on one machine using local message queues. We measured 7 seconds for the XMLSplitter that uses the DOM to extract elements and 5.3 seconds for the XSL-based Splitter. To establish a baseline, a dummy processor that consumes one message of the input queue and publishes the same message twice on the output queue took just under 2 seconds for 5,000 messages. This time includes the dummy processor consuming 5,000 messages and publishing 10,000, and the test harness consuming the 10,000 messages the processor published. So, it looks like the XSL manipulation is a little more efficient than moving elements around "by hand" (if we subtract the baseline, the XSL is about 35 percent faster). We are sure that either program could be tuned for maximum performance, but it was interesting to see them execute side by side. |
拆分器可用于将单个消息分解为一系列可以单独处理的子消息。同样,收件人列表或发布-订阅通道可用于将请求消息并行转发到多个收件人,以便获得多个响应以供选择。在大多数这些场景中,进一步处理取决于子消息的成功处理。例如,我们希望从多个供应商响应中选择最佳出价,或者我们希望在所有商品从仓库中取出后向客户收取订单费用。
A Splitter is useful to break out a single message into a sequence of submessages that can be processed individually. Likewise, a Recipient List or a Publish-Subscribe Channel is useful to forward a request message to multiple recipients in parallel in order to get multiple responses to choose from. In most of these scenarios, the further processing depends on successful processing of the submessages. For example, we want to select the best bid from a number of vendor responses or we want to bill the client for an order after all items have been pulled from the warehouse.
|
我们如何组合各个但相关的消息的结果,以便可以将它们作为一个整体进行处理? How do we combine the results of individual but related messages so that they can be processed as a whole? |
消息传递系统的异步特性使得跨多个消息收集信息变得具有挑战性。有多少条消息?如果我们向广播频道广播消息,我们可能不知道有多少收件人收听了该频道,因此无法知道预期会有多少响应。
The asynchronous nature of a messaging system makes collecting information across multiple messages challenging. How many messages are there? If we broadcast a message to a broadcast channel, we may not know how many recipients listened to that channel and therefore cannot know how many responses to expect.
即使我们使用Splitter ,响应消息也可能不会按照创建它们的顺序到达。由于各个消息可以通过不同的网络路径进行路由,因此消息传递基础设施通常可以保证每个消息的传递,但可能无法保证各个消息的传递顺序。此外,各个消息可能由不同的各方以不同的处理速度进行处理。因此,响应消息可能会无序传送(有关此问题的更详细说明,请参阅重新排序器)。
Even if we use a Splitter, the response messages may not arrive in the same sequence in which they were created. As individual messages can be routed through different network paths, the messaging infrastructure can usually guarantee the delivery of each message but may not be able to guarantee the order in which the individual messages are delivered. In addition, the individual messages may be processed by different parties with different processing speeds. As a result, response messages may be delivered out of order (see Resequencer for a more detailed description of this problem).
此外,大多数消息传递基础设施都以“最终有保证”的传递模式运行,这意味着消息可以保证传递到预期的接收者,但不能保证消息何时传递。我们应该等待消息多长时间?如果等待时间过长,可能会延迟后续处理。如果我们决定在不遗漏信息的情况下继续前进,我们就必须找到一种处理不完整信息的方法。即便如此,当丢失的消息(或多条消息)最终到达时我们该怎么办?在某些情况下,我们也许可以单独处理消息,但在其他情况下,这样做可能会导致重复处理。此外,如果我们忽略迟到的消息,我们将永久丢失其信息内容。
In addition, most messaging infrastructures operate in a "guaranteed, ultimately" delivery mode, which means that messages are guaranteed to be delivered to the intended recipient, but there are no guarantees as to when the messages will be delivered. How long should we wait for a message? If we wait too long, we may delay subsequent processing. If we decide to move ahead without the missing message, we have to find a way to work with incomplete information. Even so, what should we do when the missing message (or messages) finally arrives? In some cases, we may be able to process the message separately, but in other cases, doing so may lead to duplicate processing. Also, if we ignore the latecomer messages, we permanently lose their information content.
所有这些问题都会使多个相关消息的组合处理变得复杂。如果一个单独的组件可以处理这些复杂性并将单个消息传递给取决于所有单独子消息是否存在的后续处理业务,那么实现业务逻辑就会容易得多。
All these issues can complicate the combined processing of multiple but related messages. It would be much easier to implement the business logic if a separate component could take care of these complexities and pass a single message to the subsequent processing business that depends on the presence of all individual submessages.
|
使用有状态过滤器(聚合器)来收集和存储单个消息,直到收到一组完整的相关消息。然后,聚合器发布从各个消息中提取的单个消息。 Use a stateful filter, an Aggregator, to collect and store individual messages until it receives a complete set of related messages. Then, the Aggregator publishes a single message distilled from the individual messages. |
聚合器是一个特殊的过滤器(在管道和过滤器架构中),它接收消息流并识别相关的消息。一旦收到完整的消息集(稍后将详细介绍如何确定消息组何时完成),聚合器就会从每个相关消息中收集信息,并将单个聚合消息发布到输出通道以进行进一步处理。
The Aggregator is a special filter (in a Pipes and Filters architecture) that receives a stream of messages and identifies messages that are correlated. Once a complete set of messages has been received (more later on how to decide when a set is complete), the Aggregator collects information from each correlated message and publishes a single, aggregated message to the output channel for further processing.
与之前的大多数路由模式不同,聚合器是一个有状态组件。像基于内容的路由器这样的简单路由模式通常是无状态的,这意味着组件会一一处理传入的消息,并且不必在消息之间保留任何信息。处理消息后,此类组件的状态与消息到达之前的状态相同。因此,我们称这样的组件为无状态的。聚合器不能是无状态的,因为它需要存储每个传入的消息,直到收到所有属于一起的消息。然后,它必须将与每条消息关联的信息提取到聚合消息中。聚合器不必完整存储每条传入消息。例如,如果我们正在处理传入的拍卖出价,我们可能只需要保留最高出价和关联的出价者 ID,而不是所有单个出价消息的历史记录。尽管如此,聚合器仍必须跨消息存储一些信息,因此是有状态的。
Unlike most of the previous routing patterns, the Aggregator is a stateful component. Simple routing patterns like the Content-Based Router are often stateless, which means the component processes incoming messages one by one and does not have to keep any information between messages. After processing a message, such a component is in the same state as it was before the message arrived. Therefore, we call such a component stateless. The Aggregator cannot be stateless, since it needs to store each incoming message until all the messages that belong together have been received. Then, it must distill the information associated with each message into the aggregate message. The Aggregator does not necessarily have to store each incoming message in its entirety. For example, if we are processing incoming auction bids, we may need to keep only the highest bid and the associated bidder ID, not the history of all individual bid messages. Still, the Aggregator has to store some information across messages and is therefore stateful.
在设计Aggregator 时,我们需要指定以下属性:
When designing an Aggregator, we need to specify the following properties:
相关性: 哪些传入消息属于同一组?
Correlation: Which incoming messages belong together?
完整性条件: 我们什么时候准备好发布结果消息?
Completeness Condition: When are we ready to publish the result message?
聚合算法: 我们如何将接收到的消息组合成单个结果消息?
Aggregation Algorithm: How do we combine the received messages into a single result message?
关联通常通过传入消息的类型或显式关联标识符来实现。第 272 页描述了完整性条件和聚合算法的常见选择。
Correlation is typically achieved by either the type of the incoming messages or an explicit Correlation Identifier. Common choices for the completeness condition and aggregation algorithm are described on page 272.
由于消息系统的事件驱动性质,聚合器可以在任何时间以任何顺序接收相关消息。为了关联消息,聚合器维护活动聚合的列表,即聚合器已经接收到一些消息的聚合。当聚合器收到新消息时,它需要检查该消息是否是已存在聚合的一部分。如果不存在与此消息相关的聚合,则聚合器假定这是集合中的第一条消息并创建新的聚合。然后它将消息添加到新聚合中。如果聚合已经存在,则聚合器只需将消息添加到聚合中即可。
Due to the event-driven nature of a messaging system, the Aggregator may receive related messages at any time and in any order. To associate messages, the Aggregator maintains a list of active aggregates, that is, aggregates for which the Aggregator has received some messages already. When the Aggregator receives a new message, it needs to check whether the message is part of an already existing aggregate. If no aggregate related to this message exists, the Aggregator assumes that this is the first message of a set and creates a new aggregate. It then adds the message to the new aggregate. If an aggregate already exists, the Aggregator simply adds the message to the aggregate.
添加消息后,聚合器会评估受影响聚合的完整性条件。如果条件评估为 true,则根据聚合中累积的消息形成新的聚合消息并将其发布到输出通道。如果完整性条件评估为 false,则不会发布任何消息,并且聚合器会保持聚合处于活动状态以等待其他消息到达。
After adding the message, the Aggregator evaluates the completeness condition for the affected aggregate. If the condition evaluates to true, a new aggregate message is formed from the messages accumulated in the aggregate and published to the output channel. If the completeness condition evaluates to false, no message is published and the Aggregator keeps the aggregate active for additional messages to arrive.
下图说明了这一策略。在这个简单的场景中,传入消息通过 Correlation Identifier 关联。 当相关标识符值为100 的第一条消息到达时,聚合器会初始化一个新聚合并将该消息存储在该聚合内。在我们的示例中,完整性条件指定至少三个消息,因此聚合尚未完成。当相关标识符值为100 的第二条消息到达时,聚合器将其添加到已存在的聚合中。同样,聚合尚未完成。第三条消息指定了不同的相关标识符值101。因此,聚合器会针对该值启动新的聚合。第四消息涉及第一聚合(标识符100)。添加此消息后,聚合包含三个消息,因此现在满足完整性条件。结果,聚合器计算
The following diagram illustrates this strategy. In this simple scenario, incoming messages are related through a Correlation Identifier. When the first message with the Correlation Identifier value of 100 arrives, the Aggregator initializes a new aggregate and stores the message inside that aggregate. In our example, the completeness condition specifies a minimum of three messages, so the aggregate is not yet complete. When the second message with the Correlation Identifier value of 100 arrives, the Aggregator adds it to the already existing aggregate. Again, the aggregate is not yet complete. The third message specifies a different Correlation Identifier value, 101. As a result, the Aggregator starts a new aggregate for that value. The fourth message relates to the first aggregate (identifier 100). After adding this message, the aggregate contains three messages, so the completeness condition is now fulfilled. As a result, the Aggregator computes the aggregate message, marks the aggregate as completed, and publishes the result message.
聚合器内部结构
Aggregator Internals
每当聚合器收到无法与现有聚合关联的消息时,此策略就会创建一个新聚合。因此,聚合器不需要预先了解它可能产生的聚合。因此,我们将此变体称为自启动聚合器。
This strategy creates a new aggregate whenever the Aggregator receives a message that cannot be associated with an existing aggregate. Therefore, the Aggregator does not need prior knowledge of the aggregates that it may produce. Accordingly, we call this variant a self-starting Aggregator.
根据聚合策略,聚合器可能必须处理传入消息属于已关闭的聚合的情况,即传入消息在聚合消息发布后到达。为了避免启动新的聚合,聚合器必须保留一份已关闭的聚合列表。应定期清除此列表,以免其无限期增长。但是,我们需要小心,不要过早清除已关闭的聚合,因为这会导致任何消息延迟启动新的聚合。由于我们不需要存储完整的聚合,而只需存储它已关闭的事实,因此我们可以非常有效地存储关闭聚合的列表,并在清除算法中构建足够的安全裕度。我们还可以使用消息过期来忽略延迟时间过长的消息。
Depending on the aggregation strategy, the Aggregator may have to deal with the situation that an incoming message belongs to an aggregate that has already been closed outthat is, the incoming message arrives after the aggregate message has been published. In order to avoid starting a new aggregate, the Aggregator must keep a list of aggregates that have been closed out. This list should be purged periodically so that it does not grow indefinitely. However, we need to be careful not to purge closed-out aggregates too soon because that would cause any messages that are delayed to start a new aggregate. Since we do not need to store the complete aggregate, but just the fact that it has been closed, we can store the list of closed aggregates quite efficiently and build a sufficient safety margin into the purge algorithm. We can also use Message Expiration to ignore messages that have been delayed for an inordinate amount of time.
为了提高整体解决方案的稳健性,我们还可以允许聚合器侦听特定的控制通道,该通道允许手动清除所有活动聚合或特定聚合。如果我们想要从错误情况中恢复而无需重新启动聚合器组件,则此功能非常有用。同样,允许聚合器根据请求将活动聚合列表发布到特殊通道可能是一个非常有用的调试功能。这两个功能都是通常合并到控制总线中的功能类型的绝佳示例。
In order to increase the robustness of the overall solution, we can also allow the Aggregator to listen on a specific control channel that allows the manual purging of all active aggregates or a specific one. This feature can be useful if we want to recover from an error condition without having to restart the Aggregator component. Along the same lines, allowing the Aggregator to publish a list of active aggregates to a special channel upon request can be a very useful debugging feature. Both functions are excellent examples of the kind of features typically incorporated into a Control Bus.
对于聚合器完整性条件有多种策略。可用的策略主要取决于我们是否知道预期有多少消息。聚合器可以知道预期的子消息数量,因为它收到了原始复合消息的副本,或者因为每个单独的消息都包含总数(如拆分器示例中所述)。根据聚合器对消息流的了解程度,最常见的策略如下:
There are a number of strategies for aggregator completeness conditions. The available strategies primarily depend on whether or not we know how many messages to expect. The Aggregator could know the number of submessages to expect because it received a copy of the original composite message or because each individual message contains the total count (as described in the Splitter example). Depending on how much the Aggregator knows about the message stream, the most common strategies are as follows:
等待全部: 等待收到所有响应。这种情况很可能出现在我们之前讨论的订单示例中。不完整的订单可能没有意义。因此,如果在某个超时期限内未收到所有项目,则聚合器应引发错误条件。这种方法可能为我们的决策提供最好的基础,但也可能是最慢、最脆弱的(另外,我们需要知道预期有多少消息)。单个丢失或延迟的消息将阻止整个聚合的进一步处理。在松散耦合的异步系统中,解决此类错误情况可能是一件复杂的事情,因为消息的异步流使得难以可靠地检测错误情况(在消息“丢失”之前我们应该等待多长时间?)。处理丢失消息的一种方法是重新请求消息。然而,这种方法要求聚合器知道消息的来源,这可能会在聚合器和其他组件之间引入额外的依赖关系。
Wait for All: Wait until all responses are received. This scenario is most likely in the order example we discussed earlier. An incomplete order may not be meaningful. So, if all items are not received within a certain timeout period, an error condition should be raised by the Aggregator. This approach may give us the best basis for decision making, but may also be the slowest and most brittle (plus, we need to know how many messages to expect). A single missing or delayed message will prevent further processing of the whole aggregate. Resolving such error conditions can be a complicated matter in loosely coupled asynchronous systems because the asynchronous flow of messages makes it hard to reliably detect error conditions (how long should we wait before a message is "missing"?). One way to deal with missing messages is to re-request the message. However, this approach requires the Aggregator to know the source of the message, which may introduce additional dependencies between the Aggregator and other components.
超时: 等待指定时间长度的响应,然后通过评估在该时限内收到的响应来做出决定。如果没有收到响应,系统可能会报告异常或重试。如果对传入响应进行评分并且仅使用得分最高的消息(或少量消息),则此启发式方法非常有用。这种方式在“竞价”场景中很常见。
Timeout: Wait for a specified length of time for responses and then make a decision by evaluating those responses received within that time limit. If no responses are received, the system may report an exception or retry. This heuristic is useful if incoming responses are scored and only the message (or a small number of messages) with the highest score is used. This approach is common in "bidding" scenarios.
第一个最佳: 仅等到收到第一个(最快)响应并忽略所有其他响应。这种方法速度最快,但忽略了很多信息。这在响应时间至关重要的投标或报价场景中可能是实用的。
First Best: Wait only until the first (fastest) response is received and ignore all other responses. This approach is the fastest but ignores a lot of information. It may be practical in a bidding or quoting scenario where response time is critical.
超时覆盖: 等待指定的时间或直到收到具有预设最低分数的消息。在这种情况下,如果我们发现非常有利的回应,我们愿意提前中止;否则,我们将继续前进,直到时间到。如果此时没有找到明显的获胜者,则会对迄今为止收到的所有消息进行排名排序。
Timeout with Override: Wait for a specified amount of time or until a message with a preset minimum score has been received. In this scenario, we are willing to abort early if we find a very favorable response; otherwise, we keep going until time is up. If no clear winner was found at that point, rank ordering among all the messages received so far occurs.
外部事件: 有时,聚合会因外部业务事件的到达而结束。例如,在金融行业中,交易日结束可能标志着传入报价汇总的结束。对此类事件使用固定计时器会降低灵活性,因为它不提供太多的可变性。此外,事件消息形式的指定业务事件允许对系统进行集中控制。聚合器可以在特殊控制通道上侦听事件消息或接收指示聚合结束的特殊格式的消息。
External Event: Sometimes the aggregation is concluded by the arrival of an external business event. For example, in the financial industry, the end of the trading day may signal the end of an aggregation of incoming price quotes. Using a fixed timer for such an event reduces flexibility because it does not offer much variability. Also, a designated business event in the form of an Event Message allows for central control of the system. The Aggregator can listen for the Event Message on a special control channel or receive a specially formatted message that indicates the end of the aggregation.
与完整性条件的选择密切相关的是聚合算法的选择。以下策略是将多条消息压缩为一条消息的常见策略:
Closely tied to the selection of a completeness condition is the selection of the aggregation algorithm. The following strategies are common to condense multiple messages into a single message:
选择“最佳”答案: 此方法假设存在一个最佳答案,例如同一商品的最低出价。这使得聚合器可以做出决定并仅传递“最佳”消息。然而,在现实生活中,选择标准很少如此简单。例如,物品的最佳出价可能取决于交货时间、可用物品的数量、供应商是否在首选供应商列表上等等。
Select the "best" answer: This approach assumes that there is a single best answer, such as the lowest bid for an identical item. This makes it possible for the Aggregator to make the decision and only pass the "best" message on. However, in real life, selection criteria are rarely this simple. For example, the best bid for an item may depend on time of delivery, the number of available items, whether the vendor is on the preferred vendor list, and so on.
压缩数据: 聚合器可用于减少来自高流量源的消息流量。在这些情况下,计算单个消息的平均值或将每个消息中的数字字段添加到单个消息中可能是有意义的。如果每条消息都代表一个数值(例如收到的订单数),则此方法效果最佳。
Condense data: An Aggregator can be used to reduce message traffic from a high-traffic source. In these cases it may make sense to compute an average of individual messages or add numeric fields from each message into a single message. This works best if each message represents a numeric value, for example, the number of orders received.
收集数据以供以后评估:聚合器 并不总是能够决定如何选择最佳答案。在这些情况下,使用聚合器来收集各个消息并将它们组合成单个消息仍然是有意义的。该消息可以简单地是各个消息的数据的汇编。聚合决策可以稍后由单独的组件或人员做出。
Collect data for later evaluation: It is not always possible for an Aggregator to make the decision of how to select the best answer. In those cases it still makes sense to use an Aggregator to collect the individual messages and combine them into a single message. This message may simply be a compilation of the individual messages' data. The aggregation decision may be made later by a separate component or a human being.
在许多情况下,聚合策略是由参数驱动的。例如,等待指定时间的策略可以配置最大等待时间。同样,如果策略是等到报价超过特定阈值,我们很可能会提前让聚合器知道所需的阈值是多少。如果这些参数可在运行时配置,则聚合器可能具有可以接收控制消息(例如这些参数设置)的附加输入。控制消息还可能包含诸如期望的相关消息数量之类的信息,这可以帮助聚合器实施更有效的竣工条件。在这种情况下,聚合器不会在第一条消息到达时简单地启动新聚合,而是接收与预期消息系列相关的预先信息。该信息可以是原始请求消息(例如,分散-聚集消息)的副本,并通过任何必要的参数信息进行扩充。然后,聚合器分配一个新的聚合,并将参数信息与该聚合一起存储(见图)。当各个消息进入时,它们与相应的聚合相关联。我们将此变体称为初始化聚合器,而不是自启动聚合器。显然,只有当我们能够访问原始消息时,这种配置才是可能的,但情况可能并非总是如此。
In many instances, the aggregation strategy is driven by parameters. For example, a strategy that waits for a specified amount of time can be configured with the maximum wait time. Likewise, if the strategy is to wait until an offer exceeds a specific threshold, we will most likely let the Aggregator know in advance what the desired threshold is. If these parameters are configurable at runtime, an Aggregator may feature an additional input that can receive control messages, such as these parameter settings. The control messages may also contain information such as the number of correlated messages to expect, which can help the Aggregator implement more effective completion conditions. In such a scenario, the Aggregator does not simply start a new aggregate when the first message arrives, but rather receives up-front information related to an expected series of messages. This information can be a copy of the original request message (e.g., a Scatter-Gather message) augmented by any necessary parameter information. The Aggregator then allocates a new aggregate and stores the parameter information with the aggregate (see figure). When the individual messages come in, they are associated with the corresponding aggregate. We call this variation an initialized Aggregator as opposed to the self-starting Aggregator. This configuration, obviously, is possible only if we have access to the originating message, which may not always be the case.
聚合器在许多应用中都很有用。聚合器通常与分离器或接收者列表结合以形成复合模式。有关这些复合模式的更详细描述,请参阅组合消息处理器和分散-聚集。
Aggregators are useful in many applications. The Aggregator is often coupled with a Splitter or a Recipient List to form a composite pattern. See Composed Message Processor and Scatter-Gather for a more detailed description of these composite patterns.
|
示例: 贷款经纪人 Example: Loan Broker 第 9 章“插曲:组合消息”中的组合消息传递示例使用聚合器从银行返回的贷款报价消息中选择最佳贷款报价。贷款经纪人示例使用初始化的聚合器,通知接收者聚合器预期的报价消息数量。插曲展示了聚合器在Java 、C# 和 TIBCO 中的实现。 The composed messaging example in Chapter 9, "Interlude: Composed Messaging," uses an Aggregator to select the best loan quote from the loan quote messages returned by the banks. The loan broker example uses an initialized Aggregatorthe Recipient List informs the Aggregator of the number of quote messages to expect. The interlude shows implementations of the Aggregator in Java, C# and TIBCO. |
|
示例: 聚合器作为丢失消息检测器 Example: Aggregator as Missing Message Detector Joe Walnes 向我们展示了聚合器的创造性使用。 他的系统通过一系列组件发送消息,不幸的是,这些组件非常不可靠。即使使用保证传递也无法解决此问题,因为系统本身通常会在使用消息后发生故障。由于应用程序不是事务性客户端,因此正在进行的消息会丢失。为了帮助解决这种情况,Joe 通过两条并行路径路由传入消息:一次通过所需但不可靠的组件,一次使用保证交付绕过组件。聚合器重新组合来自两个路径的消息(见图)。 Joe Walnes showed us a creative use of an Aggregator. His system sends a message through a sequence of components, which are unfortunately quite unreliable. Even using Guaranteed Delivery will not correct this problem because the systems themselves typically fail after consuming a message. Because the applications are not Transactional Clients, the message-in-progress is lost. To help remedy this situation, Joe routes an incoming message through two parallel paths: once through the required but unreliable components and once around the components using Guaranteed Delivery. An Aggregator recombines the messages from the two paths (see figure). 具有超时功能的聚合器检测丢失的消息 An Aggregator with Timeout Detects Missing Messages 聚合器使用“超时并覆盖”完成条件,这意味着如果达到超时或已接收到两个关联消息,则聚合器完成。聚合算法取决于首先满足哪个条件。如果收到两条消息,则处理后的消息将不加修改地传递。如果发生超时事件,我们就知道其中一个组件发生故障并“吃掉”了消息。因此,我们指示聚合器发布一条错误消息,提醒操作员其中一个组件发生故障。不幸的是,必须手动重新启动组件,但更复杂的配置可能会重新启动组件并重新发送任何丢失的消息。 The Aggregator uses a "Timeout with Override" completeness condition, which means that the Aggregator completes if either the timeout is reached or the two associated messages have been received. The aggregation algorithm depends on which condition is fulfilled first. If two messages are received, the processed message is passed on without modification. If the timeout event occurs, we know that one of the components failed and "ate" the message. As a result, we instruct the Aggregator to publish an error message that alerts the operators that one of the components has failed. Unfortunately, the components have to be restarted manually, but a more sophisticated configuration could likely restart the component and resend any lost messages. |
|
示例: JMS 中的聚合器 Example: Aggregator in JMS 此示例显示使用Java 消息服务 (JMS) API 的聚合器的实现。聚合器接收一个通道上的出价消息,聚合所有相关的出价,并将出价最低的消息发布到另一个通道。出价通过拍卖 ID 属性进行关联,该属性充当消息的关联标识符。聚合策略是接收至少三个投标。聚合器是自启动的,不需要外部初始化。 This example shows the implementation of an Aggregator using the Java Messaging Service (JMS) API. The Aggregator receives bid messages on one channel, aggregates all related bids, and publishes a message with the lowest bid to another channel. Bids are correlated through an Auction ID property that acts as a Correlation Identifier for the messages. The aggregation strategy is to receive a minimum of three bids. The Aggregator is self-starting and does not require external initialization. 聚合器示例选择最低出价 The Aggregator Example Selects the Lowest Bid 该解决方案由以下主要类组成(见下图): The solution consists of the following main classes (see the following figure):
拍卖聚合器类图 Auction Aggregator Class Diagram 该解决方案的核心是Aggregator类。该类需要两个 JMS Destination:一个用于输入,另一个用于输出。目标是队列(点对点通道)或主题(发布-订阅通道)的 JMS 抽象。这种抽象允许我们编写独立于通道类型的 JMS 代码。此功能对于测试和调试非常有用。例如,在测试过程中,我们可以使用发布-订阅主题,以便我们可以轻松地“监听”消息流量。当我们投入生产时,我们可能想切换到队列。 The core of the solution is the Aggregator class. This class requires two JMS Destinations: one for input and another for output. Destination is the JMS abstraction for a Queue (Point-to-Point Channel ) or a Topic (Publish-Subscribe Channel ). The abstraction allows us to write JMS code independent from the type of channel. This feature can be very useful for testing and debugging. For example, during testing we may use publish-subscribe topics so that we can easily "listen in" on the message traffic. When we go to production, we may want to switch to queues. 公共类聚合器实现 MessageListener { 静态最终字符串 PROP_CORRID = "拍卖ID"; 映射 activeAggregates = new HashMap(); 目标输入Dest = null; 目标输出Dest = null; 会话会话=空; 消息消费者= null; MessageProducer 输出 = null; 公共聚合器(目的地输入目的地,目的地 public class Aggregator implements MessageListener { static final String PROP_CORRID = "AuctionID"; Map activeAggregates = new HashMap(); Destination inputDest = null; Destination outputDest = null; Session session = null; MessageConsumer in = null; MessageProducer out = null; public Aggregator (Destination inputDest, Destination outputDest, Session session) { this.inputDest = inputDest; this.outputDest = outputDest; this.session = session; } public void run() { try { in = session.createConsumer(inputDest); out = session.createProducer(outputDest); in.setMessageListener(this); } catch (Exception e) { System.out.println("Exception occurred: " + e .toString()); } } public void onMessage(Message msg) { try { String correlationID = msg.getStringProperty (PROP_CORRID); Aggregate aggregate = (Aggregate)activeAggregates.get (correlationID); if (aggregate == null) { aggregate = new AuctionAggregate(session); activeAggregates.put(correlationID, aggregate); } //--- ignore message if aggregate is already closed if (!aggregate.isComplete()) { aggregate.addMessage(msg); if (aggregate.isComplete()) { MapMessage result = (MapMessage)aggregate .getResultMessage(); out.send(result); } } } catch (JMSException e) { System.out.println("Exception occurred: " + e .toString()); } } } Aggregator是一个事件驱动的 Consumer,实现了MessageListener 接口,这要求它实现 onMessage方法。因为Aggregator是MessageConsumer 的消息侦听器,所以每次新消息到达使用者的目的地时,JMS 都会调用 onMesssage 方法。 对于每条传入消息,聚合器都会提取相关 ID(存储为消息属性)并检查该相关 ID 是否存在活动聚合。如果没有找到聚合,聚合器会实例化一个新的聚合AuctionAggregate 实例。然后聚合器检查聚合是否仍处于活动状态(即,不完整)。如果聚合不再处于活动状态,它将丢弃传入的消息。如果聚合处于活动状态,它将消息添加到聚合并测试是否已满足终止条件。如果是,它将获得最佳出价条目并发布。 The Aggregator is an Event-Driven Consumer and implements the MessageListener interface, which requires it to implement the onMessage method. Because the Aggregator is the message listener for the MessageConsumer, every time a new message arrives on the consumer's destination, JMS invokes the method onMesssage. For each incoming message, the Aggregator extracts the correlation ID (stored as a message property) and checks whether an active aggregate exists for this correlation ID. If no aggregate is found, the Aggregator instantiates a new AuctionAggregate instance. The Aggregator then checks whether the aggregate is still active (i.e., not complete). If the aggregate is no longer active, it discards the incoming message. If the aggregate is active, it adds the message to the aggregate and tests whether the termination condition has been fulfilled. If so, it gets the best bid entry and publishes it. 聚合器代码非常通用,仅用两行代码依赖于这个特定的示例应用程序。首先,它假设相关 ID 存储在消息属性AuctionID 中。其次,它创建 AuctionAggregate 类的实例。 如果我们使用返回 Aggregate 类型的对象并在内部创建AuctionAggregate工厂,则可以避免此引用。由于这是一本关于企业集成而不是面向对象设计的书,因此我们保持简单并忽略了这种依赖性。 The Aggregator code is very generic and depends on this specific example application only in two lines of code. First, it assumes that the correlation ID is stored in the message property AuctionID. Second, it creates an instance of the class AuctionAggregate. We could avoid this reference if we used a factory that returns an object of type Aggregate and internally creates an instance of type AuctionAggregate. Since this is a book on enterprise integration and not on object-oriented design, we kept things simple and let this dependency pass. AuctionAggregate类提供Aggregate 接口的实现。该接口相当简单,仅指定三个方法:一种用于添加新消息 ( addMessage ) ,一种用于确定聚合是否完成 ( isComplete ),一种用于获取最佳结果 ( getBestMessage ) 。 The AuctionAggregate class provides the implementation for the Aggregate interface. The interface is rather simple, specifying only three methods: one to add a new message (addMessage), one to determine whether the aggregate is complete (isComplete), and one to get the best result (getBestMessage). 公共接口聚合{
公共无效addMessage(消息消息);
公共布尔 isComplete();
公共消息 getResultMessage();
}
public interface Aggregate {
public void addMessage(Message message);
public boolean isComplete();
public Message getResultMessage();
}
我们决定创建一个单独的 Auction 类来实现聚合策略,但不依赖于 JMS API,而不是在 AuctionAggregate 类中实现聚合策略: Instead of implementing the aggregation strategy inside the AuctionAggregate class, we decided to create a separate class Auction that implements the aggregation strategy but is not dependent on the JMS API: 公开课拍卖
{
ArrayList 出价 = new ArrayList();
公共无效addBid(出价出价)
{
出价。添加(出价);
System.out.println(bids.size() + "拍卖中的出价。");
}
公共布尔值 isComplete()
{
返回 (出价.size() >= 3);
}
公开出价 getBestBid()
{
出价 bestBid = null;
迭代器 iter = bids.iterator();
if (iter.hasNext())
bestBid = (出价) iter.next();
while (iter.hasNext()) {
出价 b = (出价) iter.next();
if (b.getPrice() < bestBid.getPrice()) {
最佳出价=b;
}
}
返回最佳出价;
}
}
public class Auction
{
ArrayList bids = new ArrayList();
public void addBid(Bid bid)
{
bids.add(bid);
System.out.println(bids.size() + " Bids in auction.");
}
public boolean isComplete()
{
return (bids.size() >= 3);
}
public Bid getBestBid()
{
Bid bestBid = null;
Iterator iter = bids.iterator();
if (iter.hasNext())
bestBid = (Bid) iter.next();
while (iter.hasNext()) {
Bid b = (Bid) iter.next();
if (b.getPrice() < bestBid.getPrice()) {
bestBid = b;
}
}
return bestBid;
}
}
Auction类实际上非常简单。它提供了三个与Aggregate接口类似的方法,但方法签名的不同之处在于它们使用强类型 Bid类而不是 JMS 特定的Message类。对于此示例,资格条件非常简单,只需等待收到三个投标即可。然而,将聚合策略与Auction类和 JMS API 分开,可以轻松增强Auction类以合并更复杂的逻辑。 The Auction class is actually quite simple. It provides three methods similar to the Aggregate interface, but the method signatures differ in that they use the strongly typed Bid class instead of the JMS-specific Message class. For this example, the competeness condition is very simple, simply waiting until three bids have been received. However, separating the aggregation strategy from the Auction class and the JMS API makes it easy to enhance the Auction class to incorporate more sophisticated logic. AuctionAggregate类充当Aggregate接口和Auction 类之间的适配器 [GoF]。 适配器是将一个类的接口转换为另一个接口的类。 The AuctionAggregate class acts as an Adapter [GoF] between the Aggregate interface and the Auction class. An adapter is a class that converts the interface of a class into another interface. 公共类 AuctionAggregate 实现聚合 { static String PROP_AUCTIONID = "拍卖ID"; 静态字符串 ITEMID = "ItemID"; 静态字符串 VENDOR = "供应商"; 静态字符串 PRICE = "价格"; 私人会议; 私下拍卖; 公开拍卖聚合(会话会话) { this.session = 会话; 拍卖=新拍卖(); } 公共无效addMessage(消息消息){ 出价 出价=空; if (MapMessage 的消息实例) { 尝试 { MapMessage mapmsg = (MapMessage)消息; 字符串拍卖ID = mapmsg.getStringProperty public class AuctionAggregate implements Aggregate { static String PROP_AUCTIONID = "AuctionID"; static String ITEMID = "ItemID"; static String VENDOR = "Vendor"; static String PRICE = "Price"; private Session session; private Auction auction; public AuctionAggregate(Session session) { this.session = session; auction = new Auction(); } public void addMessage(Message message) { Bid bid = null; if (message instanceof MapMessage) { try { MapMessage mapmsg = (MapMessage)message; String auctionID = mapmsg.getStringProperty (PROP_AUCTIONID); String itemID = mapmsg.getString(ITEMID); String vendor = mapmsg.getString(VENDOR); double price = mapmsg.getDouble(PRICE); bid = new Bid(auctionID, itemID, vendor, price); auction.addBid(bid); } catch (JMSException e) { System.out.println(e.getMessage()); } } } public boolean isComplete() { return auction.isComplete(); } public Message getResultMessage() { Bid bid = auction.getBestBid(); try { MapMessage msg = session.createMapMessage(); msg.setStringProperty(PROP_AUCTIONID, bid .getCorrelationID()); msg.setString(ITEMID, bid.getItemID()); msg.setString(VENDOR, bid.getVendorName()); msg.setDouble(PRICE, bid.getPrice()); return msg; } catch (JMSException e) { System.out.println("Could not create message: " + e .getMessage()); return null; } } } 下面的序列图总结了类之间的交互: The following sequence diagram summarizes the interaction between the classes: 拍卖聚合器序列图 Auction Aggregator Sequence Diagram 这个简单的示例假设拍卖 ID 是普遍唯一的。这使我们不必担心清理公开拍卖列表,我们只是让它增长。在现实应用程序中,我们需要决定何时清除旧的拍卖记录以避免内存泄漏。 This simple example assumes that Auction IDs are universally unique. This allows us to not worry about cleaning up the open auction listwe just let it grow. In a real-life application we would need to decide when to purge old auction records to avoid memory leaks. 因为此代码仅引用 JMS Destinations ,所以我们可以使用 Topic或Queue来运行它。 在生产环境中,此应用程序可能更可能采用点对点通道(相当于 JMS队列) ,因为应该只有一个投标接收者,即聚合器。如发布-订阅通道中所述、主题可以简化测试和调试。在不影响消息流的情况下向主题添加额外的侦听器非常容易。调试消息传递应用程序时,运行一个单独的侦听器窗口来跟踪任何参与者之间交换的所有消息通常很有用。许多 JMS 实现允许您在主题名称中使用通配符,以便侦听器可以通过指定主题名称*来简单地订阅所有主题。有一个简单的侦听器工具非常方便,它可以显示有关某个主题的所有消息,并将消息记录到文件中以供以后分析。 Because this code references only JMS Destinations, we can run it with either Topics or Queues. In a production environment, this application may be more likely to employ a Point-to-Point Channel (equivalent to a JMS Queue) because there should be only a single recipient for a bid, the Aggregator. As described in Publish-Subscribe Channel, topics can simplify testing and debugging. It is very easy to add an additional listener to a topic without affecting the flow of messages. When debugging a messaging application, it is often useful to run a separate listener window that tracks all messages that are exchanged between any participants. Many JMS implementations allow you to use wildcards in topic names so that a listener can simply subscribe to all topics by specifying a topic name of *. It is very handy to have a simple listener tool that displays all messages traveling on a topic and also logs the messages into a file for later analysis. |
消息路由器可以根据消息内容或其他标准将消息从一个通道路由到不同的通道。由于各个消息可能遵循不同的路线,因此某些消息可能会比其他消息更早地通过处理步骤,从而导致消息乱序。然而,某些后续处理步骤确实需要按顺序处理消息,例如,为了维护引用完整性。
A Message Router can route messages from one channel to different channels based on message content or other criteria. Because individual messages may follow different routes, some messages are likely to pass through the processing steps sooner than others, resulting in the messages getting out of order. However, some subsequent processing steps do require in-sequence processing of messages, for example, to maintain referential integrity.
|
我们如何才能将相关但无序的消息流恢复到正确的顺序? How can we get a stream of related but out-of-sequence messages back into the correct order? |
解决乱序问题的明显方法是首先保持消息的顺序。事实上,让事物保持秩序比让它们恢复秩序更容易。这就是为什么许多大学图书馆喜欢阻止读者将书籍放回(订购的)书架。通过控制插入过程,(几乎)在任何时间点都能保证正确的顺序。但是,在处理异步消息传递解决方案时保持事情按顺序进行可能就像说服青少年让她的房间保持秩序实际上是更有效的方法一样困难。
The obvious solution to the out-of-sequence problem is to keep messages in sequence in the first place. Keeping things in order is in fact easier than getting them back in order. That's why many university libraries like to prevent readers from putting books back into the (ordered) bookshelf. By controlling the insert process, correct order is (almost) guaranteed at any point in time. But keeping things in sequence when dealing with an asynchronous messaging solution can be about as difficult as convincing a teenager that keeping her room in order is actually the more efficient approach.
事情发生混乱的一种常见方式是不同的消息采用不同的处理路径。让我们看一个简单的例子。假设我们正在处理一个编号的消息序列。如果所有偶数消息都必须经过特殊转换,而所有奇数消息都可以直接通过,那么奇数消息将立即出现在结果通道上,而偶数消息则在转换处排队。如果转换非常慢,则在单个偶数消息出现之前,所有奇数消息可能会出现在输出通道上,从而使序列完全乱序(请参见下页顶部的图)。
One common way things get out of sequence is when different messages take different processing paths. Let's look at a simple example. Let's assume we are dealing with a numbered sequence of messages. If all even-numbered messages have to undergo a special transformation, whereas all odd-numbered messages can be passed right through, then odd-numbered messages will appear on the resulting channel immediately while the even ones queue up at the transformation. If the transformation is quite slow, all odd messages may appear on the output channel before a single even message makes it, bringing the sequence completely out of order (see figure on the top of the following page).
消息乱序
Messages Getting Out of Order
为了避免消息乱序,我们可以引入一种环回(确认)机制,确保一次只有一条消息通过系统,这意味着直到最后一条消息处理完成后才会发送下一条消息。这种保守的方法可以解决这个问题,但有两个明显的缺点。首先,它会显着降低系统速度。如果我们有大量并行处理单元,我们将严重未充分利用处理能力。事实上,在许多情况下,并行处理的原因是我们需要提高性能,因此一次限制一条消息的流量将完全否定解决方案的目的。第二个问题是,这种方法要求我们控制发送到处理单元的消息。然而,我们经常发现自己处于无序消息流的接收端,而无法控制消息源。
To avoid getting the messages out of order, we could introduce a loop-back (acknowledgment) mechanism that makes sure that only one message at a time passes through the system, meaning the next message will not be sent until the last one is done processing. This conservative approach will resolve the issue, but has two significant drawbacks. First, it can slow the system significantly. If we have a large number of parallel processing units, we would severely underutilize the processing power. In fact, in many instances the reason for parallel processing is that we need to increase performance, so throttling traffic to one message at a time would completely negate the purpose of the solution. The second issue is that this approach requires us to have control over messages being sent into the processing units. However, we often find ourselves at the receiving end of an out-of-sequence message stream without having control over the message origin.
聚合器可以接收消息流,识别相关消息,并根据多种策略将它们聚合成单个消息。在此过程中,聚合器还必须处理单个消息可以在任何时间以任何顺序到达的事实。聚合器通过存储消息直到所有相关消息到达后再发布结果消息来解决此问题。
An Aggregator can receive a stream of messages, identify related messages, and aggregate them into a single message based on a number of strategies. During this process, the Aggregator also must deal with the fact that individual messages can arrive at any time and in any order. The Aggregator solves this problem by storing messages until all related messages arrive before it publishes a result message.
|
使用有状态过滤器(Resequencer)来收集消息并重新排序,以便它们可以按指定的顺序发布到输出通道。 Use a stateful filter, a Resequencer, to collect and reorder messages so that they can be published to the output channel in a specified order. |
重排序器可以接收可能不按顺序到达的消息流。它将失序的消息存储在内部缓冲区中,直到获得完整的序列,然后以正确的顺序将消息发布到输出通道。输出通道保持顺序非常重要,这样可以保证消息按顺序到达下一个组件。与大多数其他路由器一样,重排序器通常不会修改消息内容。
The Resequencer can receive a stream of messages that may not arrive in order. It stores out-of-sequence messages in an internal buffer until a complete sequence is obtained, and then publishes the messages to the output channel in the proper sequence. It is important that the output channel is order-preserving so messages are guaranteed to arrive in order at the next component. Like most other routers, a Resequencer usually does not modify the message contents.
为了使重排序器发挥作用,每条消息都必须有一个唯一的序列号(请参阅消息序列) 。该序列号不同于消息标识符或相关标识符。消息标识符是唯一标识每条消息的特殊属性。然而,在大多数情况下,消息标识符是不具有可比性的;它们基本上是随机值,通常甚至不是数字。即使它们碰巧具有数值,在现有消息标识符元素上重载序列号语义通常也是一个坏主意。相关标识符旨在将传入消息与原始出站请求进行匹配(请参阅请求-答复)。相关标识符的唯一要求是唯一性它们不必是数字或按顺序排列。因此,如果我们需要保留一系列消息的顺序,我们应该定义一个单独的字段来跟踪序列中每条消息的位置。通常,该字段可以是消息标头的一部分。
For the Resequencer to function, each message has to have a unique sequence number (see Message Sequence ). This sequence number is different from a message identifier or Correlation Identifier. A message identifier is a special attribute that uniquely identifies each message. However, in most cases, message identifiers are not comparable; they are basically random values and often not even numeric. Even if they happen to have numerical values, it is generally a bad idea to overload the sequence number semantics over an existing message identifier element. Correlation Identifiers are designed to match incoming messages to original outbound requests (see Request-Reply ). The only requirement for Correlation Identifiers is uniqueness; they do not to have to be numeric or in sequence. So, if we need to preserve the order of a series of messages, we should define a separate field to track the position of each message in the sequence. Typically, this field can be part of the message header.
生成序列号可能比生成唯一标识符更耗时。通常,可以通过组合唯一位置信息(例如,NIC的MAC地址)和当前时间以分布式方式生成唯一标识符。大多数 GUID(全局唯一标识符)算法都是这样工作的。为了生成有序数字,我们通常需要一个计数器来在整个系统中分配数字。在大多数情况下,数字仅按升序排列是不够的,还需要连续。否则,将很难识别丢失的消息。如果我们不小心,这个序列号生成器很容易成为消息流的瓶颈。如果各个消息是使用Splitter,最好将编号权合并到Splitter。[ EAA ] 中的身份字段模式包含有关如何生成密钥和序列号的有用讨论。
Generating sequence numbers can be more time consuming than generating unique identifiers. Often, unique identifiers can be generated in a distributed fashion by combining unique location information (e.g., the MAC address of the NIC) and current time. Most GUID (globally unique identifier) algorithms work this way. To generate in-sequence numbers, we generally need a single counter that assigns numbers across the system. In most cases, it is not sufficient for the numbers to be simply in ascending order, but they need to be consecutive as well. Otherwise, it will be difficult to identify missing messages. If we are not careful, this sequence number generator could easily become a bottleneck for the message flow. If the individual messages are the result of using a Splitter, it is best to incorporate the numbering right into the Splitter. The Identity Field pattern in [EAA] contains a useful discussion on how to generate keys and sequence numbers.
序列号确保重排序器可以检测到不按顺序到达的消息。但是,当乱序消息到达时,重排序器应该做什么呢?失序消息意味着具有较高序列号的消息在具有较低序列号的消息之前到达。重排序器必须存储具有较高序列号的消息,直到它接收到具有较低序列号的所有“丢失”消息。同时,它还可能收到其他乱序消息,这些消息也必须被存储。一旦缓冲区包含连续的消息序列,重定序器将此序列发送到输出通道,然后从缓冲区中删除发送的消息(见图)。
Sequence numbers ensure that the Resequencer can detect messages arriving out of sequence. But what should the Resequencer do when an out-of-sequence message arrives? An out-of-sequence message implies that a message with a higher sequence number arrives before a message with a lower sequence number. The Resequencer has to store the message with the higher sequence number until it receives all the "missing" messages with lower sequence numbers. Meanwhile, it may receive other out-of-sequence messages as well, which also have to be stored. Once the buffer contains a consecutive sequence of messages, the Resequencer sends this sequence to the output channel and then removes the sent messages from the buffer (see figure).
重排序器的内部操作
Internal Operation of the Resequencer
在这个简单的示例中,重排序器接收序列号为 1、3、5 和 2 的消息。我们假设序列以 1 开头,因此第一条消息可以立即发送并从缓冲区中删除。下一条消息的序列号为 3,因此我们丢失了消息 2。因此,我们存储消息 3,直到获得正确的消息序列。我们对下一条消息执行相同的操作,该消息的序列号为 5。一旦消息 2 进入,缓冲区将包含消息 2 和 3 的正确序列。因此,重排序器会发布这些消息并将它们从缓冲区中删除。 消息 5 保留在缓冲区中,直到序列中剩余的“间隙”被关闭。
In this simple example, the Resequencer receives messages with the sequence numbers 1, 3, 5, and 2. We assume that the sequence starts with 1, so the first message can be sent right away and removed from the buffer. The next message has the sequence number 3, so we are missing message 2. Therefore, we store message 3 until we have a proper sequence of messages. We do the same with the next message, which has a sequence number of 5. Once message 2 comes in, the buffer contains a proper sequence of the messages 2 and 3. Therefore, the Resequencer publishes these messages and removes them from the buffer. Message 5 remains in the buffer until the remaining "gap" in the sequence is closed.
缓冲区应该有多大?如果我们正在处理很长的消息流,缓冲区可能会变得相当大。更糟糕的是,假设我们有一个包含多个处理单元的配置,每个处理单元处理特定的消息类型。如果一个处理单元发生故障,我们将收到一长串无序消息。缓冲区溢出几乎是肯定的。在某些情况下,我们可以使用消息队列来吸收待处理的消息。仅当消息传递基础设施允许我们根据选择标准从队列中读取消息而不是总是先读取最旧的消息时,此方法才有效。这样,我们可以轮询队列并查看第一个丢失的消息是否已进入,而无需消耗其间的所有消息。但在某些时候,
How big should the buffer be? If we are dealing with a long stream of messages, the buffer can get rather large. Worse yet, let's assume we have a configuration with multiple processing units, each of which deals with a specific message type. If one processing unit fails, we will get a long stream of out-of-sequence messages. A buffer overrun is almost certain. In some cases, we can use the message queue to absorb the pending messages. This works only if the messaging infrastructure allows us to read messages from the queue based on selection criteria as opposed to always reading the oldest message first. That way, we can poll the queue and see whether the first missing message has come in yet without consuming all the messages in between. At some point, though, even the storage allocated to the message queue will fill up.
避免缓冲区溢出的一种可靠方法是使用主动确认来限制消息生成器(见图)。
One robust way to avoid buffer overruns is to throttle the message producer by using active acknowledgment (see figure).
主动确认避免缓冲区溢出
Active Acknowledgment Avoids Buffer Overflow
正如我们之前讨论的,一次只发送一条消息是非常低效的。我们需要比这更聪明一点。更有效的方法是重排序器告诉生产者其缓冲区中有多少个可用槽。然后,消息节流器可以触发那么多消息,因为即使它们完全乱序,重排序器也能够将所有消息保存在缓冲区中并对它们重新排序。这种方法在效率和缓冲要求之间提供了良好的折衷。但是,它确实要求我们能够访问原始的按顺序消息流,以便插入发送缓冲区和限制。
As we discussed earlier, sending only a single message at a time is very inefficient. We need to be a little smarter than that. A more efficient way is for the Resequencer to tell the producer how many slots it has available in its buffer. The message throttle can then fire off that many messages, since even if they get completely out of order, the Resequencer will be able to hold all of them in the buffer and re-sequence them. This approach presents a good compromise between efficiency and buffer requirements. However, it does require that we have access to the original in-sequence message stream in order to insert the send buffer and throttle.
这种方法与 TCP/IP 网络协议的工作方式非常相似。TCP 协议的关键功能之一是确保数据包通过网络按顺序传送。实际上,每个数据包可能会通过不同的网络路径进行路由,因此乱序数据包的出现相当频繁。接收器维护一个用作滑动窗口的循环缓冲区。接收方和发送方在每次确认之前协商要发送的数据包数量。由于发送方等待接收方的确认,因此快速发送方无法超过接收方或导致缓冲区溢出。特定的规则还可以防止所谓的愚蠢窗口综合症,即发送者和接收者可能陷入效率非常低的、一次一个数据包的模式。
This approach is very similar to the way the TCP/IP network protocol works. One of the key features of the TCP protocol is to ensure in-sequence delivery of packets over the network. In reality, each packet may be routed through a different network path, so out-of-sequence packets occur quite frequently. The receiver maintains a circular buffer that is used as a sliding window. Receiver and sender negotiate on the number of packets to send before each acknowledgment. Because the sender waits for an acknowledgment from the receiver, a fast sender cannot outpace the receiver or cause the buffer to overflow. Specific rules also prevent the so-called Silly Window Syndrome, where sender and receiver could fall into a very inefficient, one-packet-at-a-time mode.
缓冲区溢出问题的另一个解决方案是计算丢失消息的替代消息。如果接收者能够容忍“足够好”的消息数据并且不需要每条消息的精确数据或者如果速度比准确性更重要,则这种方法有效。例如,在 IP 语音传输中,填充空白数据包比对丢失的数据包发出重新请求会带来更好的用户体验,后者会导致语音流出现明显的延迟。
Another solution to the buffer overrun problem is to compute stand-in messages for the missing message. This works if the recipient is tolerant toward "good enough" message data and does not require precise data for each message or if speed is more important than accuracy. For example, in voice over IP transmissions, filling in a blank packet results in a better user experience than issuing a re-request for a lost packet, which would cause a noticeable delay in the voice stream.
我们大多数应用程序开发人员都认为可靠的网络通信是理所当然的。在设计消息传递解决方案时,研究 TCP 的一些内部结构实际上是有帮助的,因为在其核心,IP 流量是异步且不可靠的,并且必须处理许多与企业集成解决方案相同的问题。有关 IP 协议的彻底处理,请参阅 [ Stevens ] 和 [ Wright ]。
Most of us application developers take reliable network communication for granted. When designing messaging solutions, it is actually helpful to look into some of the internals of TCP, because at its core, IP traffic is asynchronous and unreliable and has to deal with many of the same issues enterprise integration solutions do. For a thorough treatment of IP protocols see [Stevens] and [Wright].
|
示例: Microsoft .NET 中使用 MSMQ 的重排序器 Example: Resequencer in Microsoft .NET with MSMQ 为了在现实场景中演示重排序器的功能,我们使用以下设置: To demonstrate the function of a Resequencer in a real-life scenario, we use the following setup: 重排序器测试配置 Resequencer Test Configuration 测试设置由四个主要组件组成,每个组件都作为 C# 类实现。这些组件通过消息队列服务提供的 MSMQ 消息队列进行通信,该服务是 Windows 2000 和 Windows XP 的一部分。 The test setup consists of four main components, each implemented as a C# class. The components communicate via MSMQ message queues provided by the Message Queuing service that is part of Windows 2000 and Windows XP.
如果我们启动所有组件,我们会看到类似于下图的调试输出。从处理器输出窗口的大小,我们可以看到处理器工作的不同速度。正如预期的那样,到达重排序器的消息不按顺序排列(在本次运行中,到达的消息为 3、4、1、5、7、2...)。我们可以从重排序器输出中看到,如果消息丢失,重排序器如何缓冲传入消息。一旦丢失的消息到达,重排序器就会以正确的顺序发布现已完成的序列。 If we fire up all components, we see debug output similar to the following figure. From the size of the processor output windows, we can see the different speeds at which the processors are working. As expected, the messages arriving at the Resequencer are not in sequence (in this run, the messages arrived as 3, 4, 1, 5, 7, 2, ...). We can see from the Resequencer output how the Resequencer buffers the incoming messages if a message is missing. As soon as the missing message arrives, the Resequencer publishes the now completed sequence in the correct order. 测试组件的输出 Output from the Test Components 查看测试设置,我们意识到 DelayProcessor和Resequencer 有一些共同点:它们都从输入队列读取消息并将其发布到输出队列。唯一的区别在于消息的实际处理之间发生的情况。因此,我们创建了一个公共基类来封装此通用过滤器的基本功能(请参阅管道和过滤器)。它包含用于队列创建以及异步接收、处理和发送消息的便捷方法和模板方法。我们称这个基类为Processor(见图)。 Looking at the test setup, we realize that both the DelayProcessor and the Resequencer have a few things in common: They both read messages from an input queue and publish them to an output queue. The only difference is in what happens in betweenthe actual processing of the message. Therefore, we created a common base class that encapsulates the basic functionality of this generic filter (see Pipes and Filters ). It contains convenience and template methods for queue creation and asynchronous receiving, processing, and sending of messages. We call this base class Processor (see figure). DelayProcessor 和 Resequencer 都继承自 Common Processor 类 Both the DelayProcessor and the Resequencer Inherit from the Common Processor Class 处理器的默认实现只是将消息从输入队列复制到输出队列。要实现Resequencer ,我们必须重写ProcessMessage。对于 Resequencer 来说,processMessage方法将接收到的消息添加到缓冲区中,该缓冲区以Hashtable 的。缓冲区中的消息由消息序列号作为键控,该消息序列号存储在AppSpecific属性中。添加新消息后,方法SendConsecutiveMessages检查我们是否有从下一个未完成消息开始的连续序列。如果是,该方法将发送所有连续消息并将它们从缓冲区中删除。 The default implementation of the Processor simply copies messages from the input queue to the output queue. To implement the Resequencer, we have to override the default implementation of the ProcessMessage method. In the case of the Resequencer, the processMessage method adds the received message in the buffer, which is implemented as a Hashtable. The messages in the buffer are keyed by the message sequence number, which is stored in the AppSpecific property. Once the new message is added, the method SendConsecutiveMessages checks whether we have a consecutive sequence starting with the next outstanding messages. If so, the method sends all consecutive messages and removes them from the buffer. |
使用系统;
使用系统消息传递;
使用系统集合;
使用消息处理器;
命名空间重排序器
{
重排序器类:处理器
{
私有 int 起始索引 = 1;
私有 IDictionary 缓冲区 = (IDictionary)(new Hashtable());
私有 int endIndex = -1;
公共重新排序器(消息队列输入队列,消息队列输出队列)
:基(输入队列,输出队列){}
protected override void ProcessMessage(消息 m)
{
添加到缓冲区(m);
发送连续消息();
}
私有无效AddToBuffer(消息m)
{
Int32 msgIndex = m.AppSpecific;
Console.WriteLine("收到的消息索引{0}", msgIndex);
if (消息索引 < 开始索引)
{
Console.WriteLine("消息索引超出范围!当前起始位置为:{0}",
开始索引);
}
别的
{
buffer.Add(msgIndex, m);
if (消息索引 > 结束索引)
结束索引=消息索引;
}
Console.WriteLine("缓冲区范围:{0} - {1}", startIndex, endIndex);
}
私有无效SendConsecutiveMessages()
{
while (buffer.Contains(startIndex))
{
消息 m = (消息)(缓冲区[startIndex]);
Console.WriteLine("正在发送索引为{0}的消息", startIndex);
输出队列.Send(m);
缓冲区.删除(startIndex);
开始索引++;
}
}
}
}
using System;
using System.Messaging;
using System.Collections;
using MsgProcessor;
namespace Resequencer
{
class Resequencer : Processor
{
private int startIndex = 1;
private IDictionary buffer = (IDictionary)(new Hashtable());
private int endIndex = -1;
public Resequencer(MessageQueue inputQueue, MessageQueue outputQueue)
: base (inputQueue, outputQueue) {}
protected override void ProcessMessage(Message m)
{
AddToBuffer(m);
SendConsecutiveMessages();
}
private void AddToBuffer(Message m)
{
Int32 msgIndex = m.AppSpecific;
Console.WriteLine("Received message index {0}", msgIndex);
if (msgIndex < startIndex)
{
Console.WriteLine("Out of range message index! Current start is: {0}",
startIndex);
}
else
{
buffer.Add(msgIndex, m);
if (msgIndex > endIndex)
endIndex = msgIndex;
}
Console.WriteLine(" Buffer range: {0} - {1}", startIndex, endIndex);
}
private void SendConsecutiveMessages()
{
while (buffer.Contains(startIndex))
{
Message m = (Message)(buffer[startIndex]);
Console.WriteLine("Sending message with index {0}", startIndex);
outputQueue.Send(m);
buffer.Remove(startIndex);
startIndex++;
}
}
}
}
正如您所看到的,重排序器假定消息序列从 1 开始。如果消息生产者也从 1 开始序列,并且两个组件在组件的生命周期内保持相同的序列,则这种方法效果很好。为了使重排序器更加灵活,消息生产者应在发送序列的第一条消息之前与重排序器协商序列起始号。此过程类似于 TCP 协议的连接序列期间交换的 SYN 消息(请参阅 [ Stevens ])。
As you can see, the Resequencer assumes that the message sequence starts with 1. This works well if the message producer also starts the sequence from 1 and the two components maintain the same sequence over the lifetime of the components. To make the Resequencer more flexible, the message producer should negotiate a sequence start number with the Resequencer before sending the first message of the sequence. This process is analogous to the SYN messages exchanged during the connect sequence of the TCP protocol (see [Stevens]).
当前的实现也没有针对缓冲区溢出的规定。我们假设DelayProcessor中止或发生故障并吃掉了一条消息。重定序器将无限期地等待丢失的消息,直到缓冲区溢出。在大容量场景中,消息和重定序器需要协商描述重定序器可以缓冲的最大消息数的窗口大小。一旦缓冲区已满,错误处理程序必须确定如何处理丢失的消息。例如,生产者可以重新发送消息,或者可以注入“虚拟”消息。
The current implementation also has no provisions for a buffer overrun. Let's assume a DelayProcessor aborts or malfunctions and eats a message. The Resequencer will wait indefinitely for the missed message until the buffer overflows. In high-volume scenarios, the message and the Resequencer need to negotiate a window size describing the maximum number of messages the Resequencer can buffer. Once the buffer is full, an error handler has to determine how to deal with the missing message. For example, the producer could resend the message, or a "dummy" message could be injected.
Processor基类相对简单。它通过使用BeginReceive 和EndReceive方法来进行异步消息处理。由于很容易忘记在消息处理结束时调用BeginReceive ,因此我们使用了包含此步骤的模板方法。然后,子类可以重写ProcessMessage 方法,而不必担心异步处理。
The Processor base class is relatively simple. It uses asynchronous message processing by using the BeginReceive and EndReceive methods. Because it is easy to forget to call BeginReceive at the end of the message processing, we used a template method that incorporates this step. Subclasses can then override the ProcessMessage method without having to worry about the asynchronous processing.
使用系统;
使用系统消息传递;
使用系统线程;
命名空间消息处理器
{
公共类处理器
{
受保护的消息队列输入队列;
受保护的消息队列输出队列;
公共处理器(消息队列输入队列,消息队列输出队列)
{
this.inputQueue = inputQueue;
this.outputQueue = 输出队列;
inputQueue.Formatter = 新System.Messaging.XmlMessageFormatter
(new String[] {"System.String,mscorlib"});
inputQueue.MessageReadPropertyFilter.ClearAll();
inputQueue.MessageReadPropertyFilter.AppSpecific = true;
inputQueue.MessageReadPropertyFilter.Body = true;
inputQueue.MessageReadPropertyFilter.CorrelationId = true;
inputQueue.MessageReadPropertyFilter.Id = true;
Console.WriteLine("正在处理来自 " + inputQueue.Path + 的消息
“到”+outputQueue.Path);
}
公共无效进程()
{
inputQueue.ReceiveCompleted += 新
ReceiveCompletedEventHandler(OnReceiveCompleted);
inputQueue.BeginReceive();
}
私人无效OnReceiveCompleted(对象源,
ReceiveCompletedEventArgs asyncResult)
{
消息队列 mq = (消息队列)源;
消息 m = mq.EndReceive(asyncResult.AsyncResult);
m.Formatter = 新 System.Messaging.XmlMessageFormatter
(new String[] {"System.String,mscorlib"});
处理消息(m);
mq.BeginReceive();
}
protected virtual void ProcessMessage(消息 m)
{
字符串主体 = (字符串)m.Body;
Console.WriteLine("收到消息:" + body);
输出队列.Send(m);
}
}
}
using System;
using System.Messaging;
using System.Threading;
namespace MsgProcessor
{
public class Processor
{
protected MessageQueue inputQueue;
protected MessageQueue outputQueue;
public Processor (MessageQueue inputQueue, MessageQueue outputQueue)
{
this.inputQueue = inputQueue;
this.outputQueue = outputQueue;
inputQueue.Formatter = new System.Messaging.XmlMessageFormatter
(new String[] {"System.String,mscorlib"});
inputQueue.MessageReadPropertyFilter.ClearAll();
inputQueue.MessageReadPropertyFilter.AppSpecific = true;
inputQueue.MessageReadPropertyFilter.Body = true;
inputQueue.MessageReadPropertyFilter.CorrelationId = true;
inputQueue.MessageReadPropertyFilter.Id = true;
Console.WriteLine("Processing messages from " + inputQueue.Path +
" to " + outputQueue.Path);
}
public void Process()
{
inputQueue.ReceiveCompleted += new
ReceiveCompletedEventHandler(OnReceiveCompleted);
inputQueue.BeginReceive();
}
private void OnReceiveCompleted(Object source,
ReceiveCompletedEventArgs asyncResult)
{
MessageQueue mq = (MessageQueue)source;
Message m = mq.EndReceive(asyncResult.AsyncResult);
m.Formatter = new System.Messaging.XmlMessageFormatter
(new String[] {"System.String,mscorlib"});
ProcessMessage(m);
mq.BeginReceive();
}
protected virtual void ProcessMessage(Message m)
{
string body = (string)m.Body;
Console.WriteLine("Received Message: " + body);
outputQueue.Send(m);
}
}
}
基于内容的路由器和拆分器模式中提供的订单处理示例处理由各个行项目组成的传入订单。每个行项目都需要使用相应的库存系统进行库存检查。验证所有商品后,我们希望将验证后的订单消息传递到下一个处理步骤。
The order-processing example presented in the Content-Based Router and Splitter patterns processes an incoming order consisting of individual line items. Each line item requires an inventory check with the respective inventory system. After all items have been verified, we want to pass the validated order message to the next processing step.
|
当处理由多个元素组成的消息(每个元素可能需要不同的处理)时,如何维护整体消息流? How can you maintain the overall message flow when processing a message consisting of multiple elements, each of which may require different processing? |
这个问题似乎包含我们已经定义的多种模式的元素。拆分器可以将单个消息拆分为多个部分。然后,基于内容的路由器可以根据消息内容或类型通过正确的处理步骤路由各个子消息。管道和过滤器架构风格允许我们将这两种模式链接在一起,以便我们可以将组合消息中的每个项目路由到适当的处理步骤:
This problem seems to contain elements of multiple patterns we have already defined. A Splitter can split a single message into multiple parts. A Content-Based Router could then route individual submessages through the correct processing steps based on message content or type. The Pipes and Filters architectural style allows us to chain together these two patterns so that we can route each item in the composed message to the appropriate processing steps:
组合分配器和路由器
Combining a Splitter and a Router
在我们的示例中,这意味着每个订单商品都会被路由到正确的库存系统进行验证。库存系统彼此解耦,每个系统仅接收其可以处理的物品。
In our example, this means that each order item is routed to the proper inventory system to be verified. The inventory systems are decoupled from each other, and each system receives only items that can be processed by it.
到目前为止,该设置的缺点是我们无法确定所有已订购的商品是否实际上都有库存并且可以发货。我们还需要检索所有商品的价格(考虑批量折扣)并将它们组合成一张发票。这要求我们继续处理,就像订单仍然是单个消息一样,即使我们只是将其分成许多子消息。
The shortcoming of the setup so far is that we cannot find out whether all items that have been ordered are actually in stock and can be shipped. We also need to retrieve the prices for all items (factoring volume discounts) and assemble them into a single invoice. This requires us to continue processing as if the order is still a single message even though we just chopped it up into many submessages.
一种方法是将通过特定库存系统的所有物品重新组装成单独的订单。从此时起,该订单可以作为一个整体进行处理:可以履行并发货订单,并且可以发送账单。每个子订单都被视为一个独立的进程。在某些情况下,缺乏对下游过程的控制可能使这种方法成为唯一可用的解决方案。例如,亚马逊对其销售的大部分商品都采用这种方法。订单被发送到不同的履行中心并从那里进行管理。
One approach would be to just reassemble all those items that pass through a specific inventory system into a separate order. This order can be processed as a whole from this point on: The order can be fulfilled and shipped, and a bill can be sent. Each suborder is treated as an independent process. In some instances, lack of control over the downstream process may make this approach the only available solution. For example, Amazon follows this approach for a large portion of the goods it sells. Orders are routed to different fulfillment houses and managed from there.
但是,这种方法可能无法提供最佳的客户体验。客户可能会收到多于一批货物和多于一张发票。退货或争议可能难以解决。对于订购书籍的消费者来说,这不是一个大问题,但如果各个订购项目相互依赖,则可能会很困难。我们假设订单包含构成搁架系统的家具物品。客户不会高兴地收到许多装有家具元件的大盒子,只是发现所需的安装硬件暂时无法提供,并将在稍后发货。
However, this approach may not provide the best customer experience. The customer may receive more than one shipment and more than one invoice. Returns or disputes may be difficult to accommodate. This is not a big issue with consumers ordering books, but may prove difficult if individual order items depend on each other. Let's assume that the order consists of furniture items that make up a shelving system. The customer would not be pleased to receive a number of huge boxes containing furniture elements just to find out that the required mounting hardware is temporarily unavailable and will be shipped at a later time.
消息传递系统的异步特性使得任务分配比同步方法调用更加复杂。我们可以发送每个单独的订单项目并等待响应返回,然后再检查下一个项目。这将简化时间依赖性,但会使系统效率非常低。我们希望利用每个系统可以同时处理多个订单的优势。
The asynchronous nature of a messaging system makes distribution of tasks more complicated than synchronous method calls. We could dispatch each individual order item and wait for a response to come back before we check the next item. This would simplify the temporal dependencies but would make the system very inefficient. We would like to take advantage of the fact that each system can process multiple orders simultaneously.
|
使用组合消息处理器来处理组合消息。组合消息处理器将消息拆分,将子消息路由到适当的目的地,并将响应重新聚合回单个消息。 Use a Composed Message Processor to process a composite message. The Composed Message Processor splits the message up, routes the submessages to the appropriate destinations, and reaggregates the responses back into a single message. |
组合消息处理器使用聚合器来协调分派到多个库存系统的请求。每个处理单元向聚合器发送一条响应消息,说明指定项目的现有库存。聚合器收集各个响应并根据预定义的算法对其进行处理。
The Composed Message Processor uses an Aggregator to reconcile the requests that were dispatched to the multiple inventory systems. Each processing unit sends a response message to the Aggregator stating the inventory on hand for the specified item. The Aggregator collects the individual responses and processes them based on a predefined algorithm.
由于所有子消息都源自一条消息,因此我们可以将附加信息(例如子消息的数量)传递给聚合器,以定义更有效的聚合策略。尽管如此,组合消息处理器仍然需要处理消息丢失或延迟的问题。如果库存系统不可用,我们是否要延迟处理包含该系统中的商品的所有订单?或者我们应该将它们路由到异常队列以供人工手动评估?如果缺少单个响应,我们是否应该重新发送库存请求消息?有关这些权衡的更详细讨论,请参阅聚合器。
Because all submessages originate from a single message, we can pass additional information, such as the number of submessages, to the Aggregator to define a more efficient aggregation strategy. Nevertheless, the Composed Message Processor still has to deal with issues around missing or delayed messages. If an inventory system is unavailable, do we want to delay processing of all orders that include items from that system? Or should we route them to an exception queue for a human to evaluate manually? If a single response is missing, should we resend the inventory request message? For a more detailed discussion of these trade-offs, see Aggregator.
该模式演示了如何将多个单独的模式组合成一个更大的模式。对于系统的其余部分,组合消息处理器看起来就像一个具有单个输入通道和单个输出通道的简单过滤器。因此,它提供了更复杂的内部工作的有效抽象。
This pattern demonstrates how several individual patterns can be composed into a single larger pattern. To the rest of the system, the Composed Message Processor appears like a simple filter with a single input channel and a single output channel. As such, it provides an effective abstraction of the more complex internal workings.
作为单个过滤器的复合消息处理器
The Composite Message Processor as a Single Filter
在前面模式中介绍的订单处理示例中,当前没有库存的每个订单项目可以由多个外部供应商之一提供。然而,供应商可能有也可能没有相应产品的库存,他们可能会收取不同的价格,或者他们可能能够在不同的日期之前供应零件。为了以尽可能最好的方式完成订单,我们应该向所有供应商请求报价,并决定哪一个供应商为我们所请求的项目提供了最佳条款。
In the order-processing example introduced in the previous patterns, each order item that is not currently in stock could be supplied by one of multiple external suppliers. However, the suppliers may or may not have the respective item in stock, they may charge a different price, or they may be able to supply the part by a different date. To fill the order in the best way possible, we should request quotes from all suppliers and decide which one provides us with the best term for the requested item.
|
当消息必须发送给多个收件人且每个收件人都可以发送回复时,如何维护整体消息流? How do you maintain the overall message flow when a message must be sent to multiple recipients, each of which may send a reply? |
该解决方案应允许灵活地确定消息的收件人。我们可以集中确定批准的供应商名单,也可以让任何感兴趣的供应商参与投标。由于我们对收件人没有(或很少)控制,因此我们必须准备好接收部分(但不是全部)收件人的回复。对投标规则的此类更改不应影响解决方案的结构完整性。
The solution should allow for flexibility in determining the recipients of the message. We can either determine the list of approved suppliers centrally or we can let any interested supplier participate in the bid. Since we have no (or little) control over the recipients, we must be prepared to receive responses from some, but not all, recipients. Such changes to the bidding rules should not impact the structural integrity of the solution.
该解决方案应在任何后续处理中隐藏各个接收者的号码和身份。在本地封装消息的分发可以使其他组件独立于各个消息的路由。
The solution should hide the number and identity of the individual recipients from any subsequent processing. Encapsulating the distribution of the message locally keeps other components independent from the route of the individual messages.
我们还需要协调后续的消息流。最简单的解决方案可能是每个收件人将回复发布到频道,并让后续组件处理各个消息的解决方案。然而,这将要求后续组件知道消息被发送给多个收件人。对于后续组件来说,在不了解已应用的路由逻辑的情况下处理各个消息也可能会更加困难。
We also need to coordinate the subsequent message flow. The easiest solution might be for each recipient to post the reply to a channel and let subsequent components deal with the resolution of the individual messages. However, this would require subsequent components to be aware of the message being sent to multiple recipients. It might also be harder for subsequent components to process the individual messages without having any knowledge of the routing logic that has been applied.
将路由逻辑、收件人和各个消息的后处理合并到一个逻辑组件中是有意义的。
It makes sense to combine the routing logic, the recipients, and the postprocessing of the individual messages into one logical component.
|
使用Scatter-Gather将消息广播给多个收件人并将响应重新聚合回单个消息。 Use a Scatter-Gather that broadcasts a message to multiple recipients and reaggregates the responses back into a single message. |
Scatter -Gather将请求消息路由到多个接收者。然后,它使用聚合器来收集响应并将它们提炼成单个响应消息。
The Scatter-Gather routes a request message to a number of recipients. It then uses an Aggregator to collect the responses and distill them into a single response message.
Scatter-Gather有两种变体,它们使用不同的机制将请求消息发送到预期的接收者:
There are two variants of the Scatter-Gather that use different mechanisms to send the request messages to the intended recipients:
通过收件人列表进行分发允许Scatter -Gather控制收件人列表,但要求Scatter -Gather了解每个收件人的消息通道。
Distribution via a Recipient List allows the Scatter-Gather to control the list of recipients but requires the Scatter-Gather to be aware of each recipient's message channel.
拍卖式的分散-聚集使用发布-订阅通道将请求广播给任何感兴趣的参与者。此选项允许分散-聚集使用单个通道,但也会强制其放弃控制。
Auction-style Scatter-Gather uses a Publish-Subscribe Channel to broadcast the request to any interested participant. This option allows the Scatter-Gather to use a single channel but also forces it to relinquish control.
该解决方案与组合消息处理器有相似之处。 我们不使用Splitter,而是使用Publish。我们很可能会添加一个返回地址,以便所有响应都可以通过单个通道进行处理。与组合消息处理器一样, Scatter -Gather根据定义的业务规则聚合响应。在我们的示例中,聚合器可能会从能够满足订单的供应商处获取最佳出价。使用Scatter-Gather聚合响应可能比使用Scatter-Gather 更困难组成消息处理器,因为我们可能不知道有多少接收者参与交互。
The solution shares similarities with the Composed Message Processor. Instead of using a Splitter, we broadcast the complete message to all involved parties using a Publish-Subscribe Channel. We will most likely add a Return Address so that all responses can be processed through a single channel. As with the Composed Message Processor, a Scatter-Gather aggregates responses based on defined business rules. In our example, the Aggregator might take the best bids from suppliers that can fill the order. Aggregating the responses can be more difficult with a Scatter-Gather than with the Composed Message Processor, because we may not know how many recipients participate in the interaction.
分散-收集和组合消息处理器都将单个消息路由到多个收件人,并使用聚合器将各个回复消息组合回单个消息。组合消息处理器执行同步多个并行活动的任务。如果各个活动花费的时间差异很大,则即使许多子任务(甚至除了一项之外的所有子任务)都已完成,后续处理也会被拖延。这种考虑需要与Scatter-Gather带来的简单性和封装性进行权衡。两种选择之间的折衷方案可能是级联聚合器。这种设计允许在仅提供结果子集的情况下启动后续任务。
Both the Scatter-Gather and the Composed Message Processor route a single message to multiple recipients and combine the individual reply messages back into a single message by using an Aggregator. The Composed Message Processor performs the task of synchronizing multiple parallel activities. If the individual activities take widely varying amounts of time, subsequent processing is held up even though many subtasks (or even all but one) have been completed. This consideration needs to be weighed against the simplicity and encapsulation the Scatter-Gather brings. A compromise between the two choices may be a cascading Aggregator. This design allows subsequent tasks to be initiated with only a subset of the results being available.
|
示例: 贷款经纪人 Example: Loan Broker 贷款经纪人示例(第 9 章,“插曲:组合消息传递”)使用Scatter-Gather将贷款报价请求路由到多个银行,并从传入响应中选择最佳报价。示例实现演示了基于接收者列表的解决方案(请参阅第 9 章中的“使用 MSMQ 的异步实现”)和发布-订阅通道(请参阅第9 章中的“使用 TIBCO ActiveEnterprise 的异步实现”)。 The Loan Broker example (Chapter 9, "Interlude: Composed Messaging") uses a Scatter-Gather to route requests for a loan quote to a number of banks and select the best offer from the incoming responses. The example implementations demonstrate both a solution based on a Recipient List see "Asynchronous Implementation with MSMQ" in Chapter 9and a Publish-Subscribe Channel see "Asynchronous Implementation with TIBCO ActiveEnterprise" in Chapter 9. |
|
示例: 组合模式 Example: Combining Patterns 我们现在可以使用Scatter-Gather来实现小部件和小工具订单处理示例。我们可以将分散-聚集与组合消息处理器结合起来,处理每个传入订单,将其排序为单独的项目,将每个项目传递给投标,将每个项目的投标聚合为组合的投标响应,然后聚合所有投标响应成完整的报价。这是一个非常真实的例子,说明了如何将多个集成模式组合成一个完整的解决方案。将单个模式组合成更大的模式使我们能够在更高的抽象级别上讨论解决方案。它还允许我们修改实现的细节而不影响其他组件。 We can now use the Scatter-Gather to implement the widget and gadget order-processing example. We can combine the Scatter-Gather with the Composed Message Processor to process each incoming order, sequence it into individual items, pass each item up for a bid, aggregate the bids for each item into a combined bid response, and then aggregate all bid responses into a complete quote. This is a very real example of how multiple integration patterns can be combined into a complete solution. The composition of individual patterns into larger patterns allows us to discuss the solution at a higher level of abstraction. It also allows us to modify details of the implementation without affecting other components. 组合分散-聚集和复合消息处理器 Combining a Scatter-Gather and a Composite Message Processor 这个例子还展示了聚合器的多功能性。 该解决方案使用两个聚合器来实现完全不同的目的。分散-聚集的第一个聚合器部分从许多供应商中选择最佳出价。该聚合器可能不需要所有供应商的响应(速度可能比低价更重要),但可能需要复杂的算法来组合响应。例如,订单可能包含 100 个小部件,而最低价格的供应商只有 60 个小部件库存。聚合商必须能够决定是否接受此报价并填写其他供应商的剩余 40 个项目。第二个聚合器组合消息处理器的一部分可能更简单,因为它只是连接从第一个聚合器收到的所有响应。然而,该聚合器需要确保所有响应实际上都已收到,并且需要处理错误情况,例如缺少项目响应。 This example also shows the versatility of the Aggregator. The solution uses two Aggregators for quite different purposes. The first Aggregator part of the Scatter-Gatherchooses the best bid from a number of vendors. This Aggregator may not require a response from all vendors (speed may be more important than a low price) but may require a complex algorithm to combine responses. For example, the order may contain 100 widgets, and the lowest price supplier has only 60 widgets in stock. The Aggregator must be able to decide whether to accept this offer and fill the remaining 40 items from another supplier. The second Aggregator part of the Composed Message Processor might be simpler because it simply concatenates all responses received from the first Aggregator. However, this Aggregator needs to make sure that all responses are in fact received and needs to deal with error conditions such as missing item responses. |
到目前为止,大多数路由模式都会根据一组规则将传入消息路由到一个或多个目的地。但有时,我们不仅需要将消息路由到单个组件,还需要通过整个系列的组件。例如,假设我们使用管道和过滤器用于处理必须经过一系列处理步骤和业务规则验证的传入消息的体系结构。由于验证的性质差异很大,并且可能取决于外部系统(例如信用卡验证),因此我们将每种类型的步骤实现为单独的过滤器。每个过滤器都会检查传入的消息并将业务规则应用于该消息。如果消息不满足规则指定的条件,则将其路由到异常通道。过滤器之间的通道决定消息需要经历的验证顺序。
Most of the routing patterns presented so far route incoming messages to one or more destinations based on a set of rules. Sometimes, though, we need to route a message not just to a single component, but through a whole series of components. Let's assume, for example, that we use a Pipes and Filters architecture to process incoming messages that have to undergo a sequence of processing steps and business rule validations. Since the nature of the validations varies widely and may depend on external systems (e.g., credit card validations), we implement each type of step as a separate filter. Each filter inspects the incoming message and applies the business rule(s) to the message. If the message does not fulfill the conditions specified by the rules, it is routed to an exception channel. The channels between the filters determine the sequence of validations that the message needs to undergo.
不过,现在我们假设对每条消息执行的验证集取决于消息类型(例如,采购订单请求不需要信用卡验证,或者通过 VPN 发送订单的客户可能不需要解密和身份验证) 。为了满足这一要求,我们需要找到一种配置,可以根据消息的类型通过不同的过滤器序列路由消息。
Now let's assume, though, that the set of validations to perform against each message depends on the message type (for example, purchase order requests do not need credit card validation, or customers who send orders over a VPN may not require decryption and authentication). To accommodate this requirement, we need to find a configuration that can route the message through a different sequence of filters depending on the type of the message.
|
当设计时步骤顺序未知并且每条消息可能有所不同时,我们如何通过一系列处理步骤连续路由消息? How do we route a message consecutively through a series of processing steps when the sequence of steps is not known at design time and may vary for each message? |
管道和过滤器架构风格为我们提供了一种优雅的方法,将一系列处理步骤表示为通过管道(通道)连接的独立过滤器。在默认配置中,过滤器通过固定管道连接。如果我们希望允许消息动态路由到不同的过滤器,我们可以使用充当消息路由器的特殊过滤器。路由器动态地确定将消息路由到的下一个过滤器。
The Pipes and Filters architectural style gives us an elegant approach to represent a sequence of processing steps as independent filters connected by pipes (channels). In its default configuration, the filters are connected by fixed pipes. If we want to allow messages to be routed to different filters dynamically, we can use special filters that act as Message Routers. The routers dynamically determine the next filter to route the message to.
好的解决方案的关键要求可以总结如下:
The key requirements for a good solution to our problem can be summarized as follows:
高效的消息流: 消息应仅流经所需的步骤,并避免不必要的组件。
Efficient message flow: Messages should flow through only the required steps and avoid unnecessary components.
资源的高效利用: 解决方案不应使用大量的通道、路由器和其他资源。
Efficient use of resources: The solution should not use a huge amount of channels, routers, and other resources.
灵活: 各个消息所采用的路线应该易于更改。
Flexible: The route that individual messages take should be easy to change.
易于维护: 如果需要支持新类型的消息,我们希望有单点维护以避免引入错误。
Simple to maintain: If a new type of message needs to be supported, we would like to have a single point of maintenance to avoid introducing errors.
下图说明了我们对该问题的替代解决方案。我们假设系统提供三个独立的处理步骤A、B和C,并且当前消息只需要经过步骤A和C。该示例消息的实际流程用粗箭头标记。
The following diagrams illustrate our alternative solutions to the problem. We assume that the system offers three separate processing steps, A, B, and C, and that the current message is required to pass only through steps A and C. The actual flow for this example message is marked with thick arrows.
我们可以形成一个由所有可能的验证步骤组成的长管道和过滤器链,并向每个路由器添加代码以绕过验证(如果所传递的消息类型不需要该步骤)(请参阅选项 A)。该选项本质上采用了消息过滤器中描述的反应式过滤方法。虽然该解决方案的简单性很有吸引力,但组件混合了业务逻辑(验证)和路由逻辑(决定是否验证)这一事实将使它们更难以重用。此外,可以想象两种类型的消息经历相似的处理步骤但顺序不同。这种硬连线方法不容易支持这一要求。
We could form one long Pipes and Filters chain of all possible validation steps and add code to each router to bypass the validation if the step is not required for the type of message being passed through (see Option A). This option essentially employs the reactive filtering approach described in the Message Filter. While the simplicity of this solution is appealing, the fact that the components blend both business logic (the validation) and routing logic (deciding whether to validate) will make them harder to reuse. Also, it is conceivable that two types of messages undergo similar processing steps but in different order. This hard-wired approach would not easily support this requirement.
为了改善关注点分离并增加解决方案的可组合性,我们应该用基于内容的路由器替换每个组件内部的“门控”逻辑。然后,我们将到达所有可能的验证步骤链,每个步骤都以基于内容的路由器为前缀(请参阅选项 B)。当消息到达路由器时,它会检查消息的类型并确定该类型的消息是否需要验证一步在手。如果需要该步骤,路由器将通过验证路由消息。如果不需要该步骤,路由器将绕过验证并将消息直接路由到下一个路由器(以与 Detour 非常相似的方式)545)。在每个步骤独立于任何其他步骤并且可以在每个步骤本地做出路由决策的情况下,此配置非常有效。不利的一面是,这种方法最终会通过一长串路由器来路由消息,即使只执行了几个验证步骤。事实上,每条消息将以两倍于可能组件数量的速率通过通道传输。如果我们有一个很大的组件库,这将导致一个相当简单的功能产生大量的消息流。此外,路由逻辑分布在许多过滤器中,因此很难理解特定类型的消息实际上将经历哪些验证步骤。同样,如果我们引入新的消息类型,我们可能必须更新每个路由器。最后,
In order to improve separation of concerns and increase the composability of the solution, we should replace the "gating" logic inside each component with Content-Based Routers. We would then arrive at a chain of all possible validation steps, each prefixed by a Content-Based Router see Option B. When a message arrives at the router, it checks the type of the message and determines whether this type of message requires the validation step at hand. If the step is required, the router routes the message through the validation. If the step is not required, the router bypasses the validation and routes the message directly to the next router (in a way very similar to the Detour 545). This configuration works quite well in cases where each step is independent from any other step and the routing decision can be made locally at each step. On the downside, this approach ends up routing the message though a long series of routers even though only a few validation steps may be executed. In fact, each message will be transmitted across a channel at a rate of two times the number of possible components. If we have a large component library, this will cause an enormous amount of message flow for a rather simple function. Also, the routing logic is distributed across many filters, making it hard to understand which validation steps a message of a specific type will actually undergo. Likewise, if we introduce a new message type, we may have to update each and every router. Finally, this option suffers from the same limitation as Option A in that messages are tied to executing steps in a predefined order.
如果我们需要一个中心控制点,那么在我们之前的模式讨论中,预先的基于内容的路由器往往是一个不错的选择。我们可以设想一个解决方案,为每种消息类型设置单独的管道和过滤器链。每个链将包含与特定类型相关的验证序列。然后我们将使用基于内容的路由器根据消息类型将传入消息路由到正确的验证链(请参阅选项 C)。此方法仅通过相关步骤(加上初始路由器)路由消息。因此,这是迄今为止最有效的方法,因为我们仅添加一个路由步骤来实现所需的功能。该解决方案还强调了特定类型的消息将采取的路径非常好。然而,它要求我们硬连接任何可能的验证规则组合。此外,同一组件可以用在多个路径中。这种方法需要我们运行此类组件的多个实例,这会导致不必要的重复。对于大量消息类型,这种方法可能会导致维护噩梦,因为我们必须维护大量的组件实例和关联通道。综上所述,该解决方案非常高效,但牺牲了可维护性。
If we desire a central point of control, an up-front Content-Based Router tended to be a good choice in our prior pattern discussions. We could envision a solution where we set up individual Pipes and Filters chains for each message type. Each chain would contain the sequence of validations relevant to a specific type. We would then use a Content-Based Router to route the incoming message to the correct validation chain based on message type (see Option C). This approach routes messages only through the relevant steps (plus the initial router). Thus, it is the most efficient approach so far because we add only a single routing step in order to implement the desired functionality. The solution also highlights the path that a message of a specific type will take quite nicely. However, it requires us to hard-wire any possible combination of validation rules. Also, the same component may be used in more than one path. This approach would require us to run multiple instances of such components, which leads to unnecessary duplication. For a large set of message types, this approach could result in a maintenance nightmare because of the large number of component instances and associated channels that we have to maintain. In summary, this solution is very efficient at the expense of maintainability.
如果我们想避免硬连接验证步骤的所有可能组合,我们需要在每个验证步骤之间插入一个基于内容的路由器(请参阅选项 D)。为了避免遇到与反应式过滤方法(选项 B 中介绍)相关的相同问题,我们将在每个步骤之后而不是之前插入基于内容的路由器(我们需要在第一步之前添加一个额外的路由器来开始吧)。路由器足够聪明,可以将消息直接转发到下一个所需的验证步骤,而不是盲目地将其路由到下一个可用的验证步骤链中的一步。从抽象的角度来看,该解决方案看起来类似于反应式过滤方法,因为消息会遍历一组交替的路由器和过滤器。然而,在这种情况下,路由器比简单的是或否决策拥有更多智能,这使我们能够消除不必要的步骤。例如,在我们的简单场景中,消息仅通过两个路由器,而不是使用选项 B 的三个路由器。此选项提供了效率和灵活性,但并没有解决我们获得中央控制的目标,我们仍然必须维护潜在的大量路由器,因为路由逻辑分布在一系列独立的路由器上。
If we want to avoid hard-wiring all possible combinations of validation steps, we need to insert a Content-Based Router between each validation step (see Option D). In order not to run into the same issues associated with the reactive filtering approach (presented in Option B), we would insert the Content-Based Router after each step instead of before (we need one additional router in front of the very first step to get started). The routers would be smart enough to relay the message directly to the next required validation step instead of routing it blindly to the next available step in the chain. In the abstract, this solution looks similar to the reactive filtering approach because the message traverses an alternating set of routers and filters. However, in this case the routers possess more intelligence than a simple yes or no decision, which allows us to eliminate unnecessary steps. For example, in our simple scenario, the message passes only through two routers as opposed to three with Option B. This option provides efficiency and flexibility but does not solve our goal of obtaining central controlwe still have to maintain a potentially large number of routers because the routing logic is spread out across a series of independent routers.
为了解决最后一个缺点,我们可以将所有路由器组合成一个“超级路由器”(参见选项 E)。在每个验证步骤之后,消息将被路由回超级路由器,超级路由器将确定要执行的下一个验证步骤。此配置仅将消息路由到特定消息类型所需的过滤器。由于所有路由决策现在都合并到单个路由器中,因此我们需要设计一种机制来记住我们已经完成处理的步骤。因此,超级路由器必须是有状态的,或者每个过滤器必须在消息上附加一个标签,告诉超级路由器该消息经过的最后一个过滤器的名称。还,我们仍在处理这样一个事实:每个验证步骤都需要通过两个通道传递消息:传递到组件,然后返回到超级路由器。这导致的流量大约是选项 C 的两倍。
To address this last shortcoming, we could combine all routers into a single "super-router" (see Option E). After each validation step, the message would be routed back to the super-router, which would determine the next validation step to be executed. This configuration routes the message only to those filters that are required for the specific type of message. Since all the routing decisions are now incorporated into a single router, we need to devise a mechanism to remember which steps we already finished processing. Therefore, the super-router would have to be stateful or each filter would have to attach a tag to the message telling the super-router the name of the last filter the message went through. Also, we are still dealing with the fact that each validation step requires the message to be passed through two channels: to the component and then back to the super-router. This results in about two times as much traffic as Option C.
|
将路由单附加到每条消息,指定处理步骤的顺序。用一个特殊的消息路由器包装每个组件,该路由器读取路由表并将消息路由到列表中的下一个组件。 Attach a Routing Slip to each message, specifying the sequence of processing steps. Wrap each component with a special message router that reads the Routing Slip and routes the message to the next component in the list. |
我们在流程的开头插入一个特殊组件,用于计算每条消息所需步骤的列表。然后,它将列表作为路由表附加到消息,并通过将消息路由到第一个处理步骤来启动该流程。成功处理后,每个处理步骤都会查看路由表并将消息传递到路由表中指定的下一个处理步骤。
We insert a special component into the beginning of the process that computes the list of required steps for each message. It then attaches the list as a Routing Slip to the message and starts the process by routing the message to the first processing step. After successful processing, each processing step looks at the Routing Slip and passes the message to the next processing step specified in the routing table.
此模式的工作原理类似于附在杂志上以便在小组或部门中流通的传送单。唯一的区别是,路由表有一个明确的遍历组件顺序,而在大多数公司中,你可以在阅读完杂志后将其交给列表中任何未读过的人(当然,老板通常是第一位的) 。
This pattern works similarly to the routing slip attached to a magazine for circulation in a group or department. The only difference is that the Routing Slip has a defined sequence of components it traverses, whereas in most companies you can hand the magazine after reading it to any person on the list who has not read it (of course, the boss usually comes first).
Routing Slip将超级路由器方法(选项 E)的中央控制与硬连线解决方案(选项 C)的效率相结合。我们预先确定完整的路由方案并将其附加到消息中,因此我们不必返回中央路由器进行进一步决策。每个组件都通过简单的路由逻辑进行了增强。在所提出的解决方案中,我们假设该路由逻辑内置于处理组件本身中。如果我们回顾一下选项 A,我们会记得我们之所以放弃这种方法,部分原因是我们必须将一些逻辑硬编码到每个组件中。路由表如何更好?主要区别在于路由表中使用的路由器是通用的,不必随着路由逻辑的变化而改变。合并到每个组件中的路由逻辑类似于返回地址,其中返回地址是从地址列表中选择的。与Return Address 类似,即使组件中内置了一段路由逻辑,组件仍保留其可重用性和可组合性。此外,路由表的计算现在可以在中心位置完成,而无需触及任何处理组件内的代码。
The Routing Slip combines the central control of the super-router approach (Option E) with the efficiency of the hard-wired solution (Option C). We determine the complete routing scheme up front and attach it to the message, so we do not have to return to the central router for further decision making. Each component is augmented by simple routing logic. In the proposed solution, we assume that this routing logic is built into the processing component itself. If we look back at Option A, we remember that we dismissed this approach partly because we had to hard-code some logic into each component. How is the Routing Slip better? The key difference is that the router used in the Routing Slip is generic and does not have to change with changes in the routing logic. The routing logic incorporated into each component is similar to a Return Address, where the return address is selected from a list of addresses. Similarly to the Return Address, the components retain their reusability and composability even though a piece of routing logic is built into the component. Additionally, the computation of the routing table can now be done in a central place without ever touching the code inside any of the processing components.
一如既往,天下没有免费的午餐,因此我们可以预期路由表会有一些限制。首先,消息大小略有增加。在大多数情况下,这应该是微不足道的,但我们需要意识到我们现在在消息中携带进程状态(哪些步骤已完成)。这可能会导致其他副作用。例如,如果我们丢失一条消息,我们不仅会丢失消息数据,还会丢失流程数据(即消息接下来要去的地方)。在许多情况下,将所有消息的状态维护在一个中心位置以执行报告或错误恢复可能很有用。
As always, there is no free lunch, so we can expect the Routing Slip to have some limitations. First, the message size increases slightly. In most cases, this should be insignificant, but we need to realize that we are now carrying process state (which steps have been completed) inside the message. This can cause other side effects. For example, if we lose a message, we lose not only the message data but also the process data (i.e., where the message was going to go next). In many cases, it may be useful to maintain the state of all messages in a central place to perform reporting or error recovery.
路由表的另一个限制是消息的路径一旦开始就不能更改。这意味着消息路径不能依赖于沿途处理步骤生成的中间结果。不过,在许多现实业务流程中,消息流确实会根据中间结果而发生变化。例如,根据订购商品的可用性(如库存系统报告),我们可能希望继续使用不同的路径。这也意味着中央实体必须能够提前确定消息应经历的所有步骤。这可能会导致设计中出现一些脆弱性,类似于使用基于内容的路由器的担忧。
Another limitation of the Routing Slip is that the path of a message cannot be changed once it is underway. This implies that the message path cannot depend on intermediate results generated by a processing step along the way. In many real-life business processes the message flow does change based on intermediate results, though. For example, depending on the availability of the ordered items (as reported by the inventory system), we may want to continue with a different path. This also means that a central entity has to be able to determine all steps a message should undergo in advance. This can lead to some brittleness in the design, similar to the concerns about using a Content-Based Router.
路由表假设我们有能力使用路由器逻辑来增强各个组件。如果我们处理遗留应用程序或打包应用程序,我们可能无法影响组件本身的功能。相反,我们需要使用外部路由器通过消息传递与组件进行通信。这不可避免地增加了使用的通道和组件的数量。然而,路由表仍然提供了我们的效率、灵活性和可维护性目标之间的最佳权衡。
The Routing Slip assumes that we have the ability to augment the individual components with the router logic. If we are dealing with legacy applications or packaged applications, we may not be able to influence the functionality of the component itself. Rather, we need to use an external router that communicates with the component via messaging. This inevitably increases the number of channels and components in use. However, the Routing Slip still provides the best trade-off between our goals of efficiency, flexibility, and maintainability.
使用旧应用程序实施路由表
Implementing a Routing Slip with Legacy Applications
路由表在以下场景中最有用:
The Routing Slip is most useful in the following scenarios:
一系列二进制验证步骤。通过不在消息中添加信息,一旦消息正在进行就无法更改路由的限制不再是一个因素。我们仍然赞赏通过重新配置中央路由表来更改验证步骤顺序的灵活性。每个组件都可以选择因错误而中止序列或将消息传递到下一步。
A sequence of binary validation steps. By not adding information to the message, the limitation that we cannot change the routing once the message is underway is no longer a factor. We still appreciate the flexibility to change the sequence of validation steps by reconfiguring the central Routing Slip. Each component has the choice between aborting the sequence due to error or passing the message on to the next step.
每一步都是无状态转换。例如,假设我们收到来自多个业务合作伙伴的订单。所有订单均通过公共渠道到达,但根据合作伙伴的不同采用不同的格式。因此,每条消息可能需要不同的转换步骤。部分合作伙伴的消息可能需要解密;其他人可能不会。有些可能需要转型或丰富;其他人可能不会。为每个合作伙伴保留一个路由表让我们可以轻松地在中央位置为每个合作伙伴重新配置步骤。
Each step is a stateless transformation. For example, let's assume that we receive orders from a variety of business partners. All orders arrive on a common channel but in different formats depending on the partner. As a result, each message may require different transformation steps. Messages from some partners may require decryption; others may not. Some may require transformation or enrichment; others may not. Keeping a Routing Slip for each partner gives us an easy way to reconfigure the steps for each partner in a central location.
每个步骤都会收集数据,但不做出任何决定(请参阅内容丰富器)。在某些情况下,我们会收到一条包含其他数据的引用标识符的消息。例如,如果我们收到 DSL 线路的订单,则该消息可能仅包含申请人的家庭电话号码。我们需要通过外部来源来确定客户的名称、为线路提供服务的中心办公室、距中心办公室的距离等。一旦我们获得包含所有相关数据的完整消息,我们就可以决定向客户提供什么套餐。在这种情况下,决策会被推迟到最后,因此我们可以使用路由表来收集必要的信息。不过,我们需要评估我们是否真的需要灵活性路由表。否则,管道和过滤器的简单硬连线链可能就足够了。
Each step gathers data, but makes no decisions (see Content Enricher ). In some cases, we receive a message that contains reference identifiers to other data. For example, if we receive an order for a DSL line, the message may contain only the home phone number of the applicant. We need to go to external sources to determine the customer's name, the central office servicing the line, the distance from the central office, and so on. Once we have a complete message with all relevant data, we can decide what package to offer to the customer. In this scenario, the decision is postponed until the end, so we can use a Routing Slip to collect the necessary information. We need to assess, though, whether we really require the flexibility of the Routing Slip. Otherwise, a simple hard-wired chain of Pipes and Filters may be sufficient.
基于内容的路由器的缺点之一是它必须合并有关每个可能的接收者以及与该接收者关联的路由规则的知识。在松散耦合的精神下,可能不希望有一个包含许多其他组件知识的中心组件。基于内容的路由器的替代解决方案是发布-订阅通道与消息过滤器数组相结合如消息过滤器中所述。该解决方案允许每个收件人决定处理哪些消息,但存在重复消息处理的风险。使各个收件人能够决定是否处理给定消息的另一个选项是使用路由单的修改版本作为责任链,如 [GoF] 中所述。 责任链允许每个组件接受消息或将其路由到列表中的下一个组件。路由表是所有参与者的静态列表。这仍然意味着中心组件必须了解所有可能的接收者。然而,组件不需要知道每个组件消耗了哪些消息。
One of the downsides of a Content-Based Router is that it has to incorporate knowledge about each possible recipient and the routing rules associated with that recipient. Under the spirit of loose coupling, it may be undesirable to have a central component that incorporates knowledge about many other components. An alternative solution to the Content-Based Router is a Publish-Subscribe Channel combined with an array of Message Filters as described in the Message Filter. This solution allows each recipient to decide which messages to process but suffers from risk of duplicate message processing. Another option to enable individual recipients to decide whether to process a given message is to use a modified version of a Routing Slip acting as a Chain of Responsibility as described in [GoF]. The Chain of Responsibility allows each component to accept a message or route it to the next component in the list. The Routing Slip is a static list of all participants. This still implies that a central component has to have knowledge of all possible recipients. However, the component does not need to know which messages each component consumes.
使用路由表可以避免重复消息处理的风险。同样,很容易确定消息是否未被任何组件处理。主要的权衡是处理速度较慢和网络流量增加。基于内容的路由器无论系统数量如何都会发布单个消息,而路由表方法发布的平均消息数量等于系统数量的一半。如果我们能够以这样的方式安排系统,使得第一个接收消息的系统有更高的机会处理该消息,那么我们可以减少这个数字,但消息的数量可能仍然高于基于内容的预测路由器。
Using a Routing Slip avoids the risk of duplicate message processing. Likewise, it is easy to determine if a message was not processed by any component. The main trade-off is the slower processing and increased network traffic. While a Content-Based Router publishes a single message regardless of the number of systems, the Routing Slip approach publishes an average number of messages equal to half the number of systems. We can reduce this number if we can arrange the systems in such a way that the first systems to receive the message have a higher chance of handling the message, but the number of messages will likely remain higher than with a predictive Content-Based Router.
在某些情况下,我们需要比简单的顺序列表更多的控制,或者我们需要根据中间结果更改消息流。Process Manager可以满足这些要求,因为它支持分支条件、分叉和连接。实质上,路由表是动态配置的业务流程的特例。应仔细检查使用路由表和使用中央流程管理器之间的权衡。动态路由表结合了中心维护点的优势和硬连线解决方案的效率。然而,随着复杂性的增加,分析和调试系统可能变得越来越困难,因为路由状态信息分布在消息中。此外,随着流程定义的语义开始包含决策、分叉和连接等结构,配置文件可能会变得难以理解和维护。我们可以在路由表中包含条件语句,并扩充每个组件中的路由模块来解释条件命令以确定下一个路由位置。不过,我们需要小心,不要因为额外的功能而使该解决方案的简单性负担过重。Routing Slip并开始使用功能更强大的流程管理器。
There are cases where we need more control than a simple sequential list or we need to change the flow of a message based on intermediate results. The Process Manager can fulfill these requirements because it supports branching conditions, forks, and joins. In essence, the Routing Slip is a special case of a dynamically configured business process. The trade-offs between using a Routing Slip and using a central Process Manager should be carefully examined. A dynamic Routing Slip combines the benefits of a central point of maintenance with the efficiency of a hard-wired solution. However, as the complexity grows, analyzing and debugging the system may become increasingly difficult, since the routing state information is distributed across the messages. Also, as the semantics of the process definition begin to include constructs such as decisions, forks, and joins, the configuration file may become hard to understand and maintain. We could include conditional statements inside the routing table and augment the routing modules in each component to interpret the conditional commands to determine the next routing location. We need to be careful, though, to not overburden the simplicity of this solution with additional functionality. Once we require this type of complexity, it may be a good idea to give up the runtime efficiency of a Routing Slip and to start using a much more powerful Process Manager.
|
示例: 作为组合服务的路由表 Example: Routing Slip as a Composed Service 创建面向服务的体系结构时,单个逻辑功能通常由多个独立步骤组成。这种情况的发生通常有两个主要原因。首先,打包应用程序倾向于公开基于其内部 API 的细粒度接口。当将这些包集成到集成解决方案中时,我们希望在更高的抽象级别上工作。例如,“新帐户”操作可能需要计费系统内的多个步骤:创建新客户、选择服务计划、设置地址属性、验证信用数据等。其次,单个逻辑功能可能分布在多个系统中。我们希望向其他系统隐藏这一事实,以便我们可以灵活地在系统之间重新分配职责,而不会影响集成解决方案的其余部分。我们可以轻松地使用路由滑动执行多个内部步骤以响应单个请求消息。路由表使我们能够灵活地从同一通道执行不同的请求。路由表执行各个步骤的序列,但在外部看来就像单个步骤(参见下页的图)。 When creating a service-oriented architecture, a single logical function is often composed of multiple independent steps. This situation occurs commonly for two primary reasons. First, packaged applications tend to expose fine-grained interfaces based on their internal APIs. When integrating these packages into an integration solution, we want to work at a higher level of abstraction. For example, the operation "New Account" may require multiple steps inside a billing system: Create a new customer, select a service plan, set address properties, verify credit data, and so on. Second, a single logical function may be spread across more than one system. We want to hide this fact from other systems so we have the flexibility to reassign responsibilities between systems without affecting the rest of the integration solution. We can easily use a Routing Slip to execute multiple internal steps in response to a single request message. The Routing Slip gives us the flexibility to execute different requests from the same channel. The Routing Slip executes the sequence of individual steps but appears to the outside like a single step (see figure on the following page). 使用路由表作为组合服务 Using a Routing Slip as a Composed Service
|
|
示例: WS 路由 Example: WS-Routing 通常,Web 服务请求必须通过多个中介进行路由。为此,Microsoft 定义了 Web 服务路由协议 (WS-Routing) 规范。WS-Routing 是一种基于 SOAP 的协议,用于将消息从发送方通过一系列中介路由到接收方。WS-Routing 的语义比Routing Slip更丰富,但是Routing Slip可以在 WS-Routing 中轻松实现。以下示例显示了通过中介 B 和 C(由元素<wsrp:via>表示)从节点 A 路由到节点 D 的消息的 SOAP 标头。 Frequently, a Web service request has to be routed through multiple intermediaries. For this purpose, Microsoft defined the Web Services Routing Protocol (WS-Routing) specification. WS-Routing is a SOAP-based protocol for routing messages from a sender through a series of intermediaries to a receiver. The semantics of WS-Routing are richer than those of the Routing Slip, but a Routing Slip can be easily implemented in WS-Routing. The following example shows the SOAP header for a message that is routed from node A to node D via the intermediaries B and C (denoted by the element <wsrp:via>). <SOAP-ENV:信封
xmlns:SOAP-ENV="http://www.w3.org/2001/06/soap-envelope">
<SOAP-ENV:标头>
<wsrp:路径 xmlns:wsrp="http://schemas.xmlsoap.org/rp/">
<wsrp:action>http://www.im.org/chat</wsrp:action>
<wsrp:to>soap://D.com/some/endpoint</wsrp:to>
<wsrp:转发>
<wsrp:via>soap://B.com</wsrp:via>
<wsrp:via>soap://C.com</wsrp:via>
</wsrp:转发>
<wsrp:from>soap://A.com/some/endpoint</wsrp:from>
<wsrp:id>uuid:84b9f5d0-33fb-4a81-b02b-5b760641c1d6</wsrp:id>
</wsrp:路径>
</SOAP-ENV:标头>
<SOAP-ENV:正文>
...
</SOAP-ENV:正文>
</SOAP-ENV:信封>
<SOAP-ENV:Envelope
xmlns:SOAP-ENV="http://www.w3.org/2001/06/soap-envelope">
<SOAP-ENV:Header>
<wsrp:path xmlns:wsrp="http://schemas.xmlsoap.org/rp/">
<wsrp:action>http://www.im.org/chat</wsrp:action>
<wsrp:to>soap://D.com/some/endpoint</wsrp:to>
<wsrp:fwd>
<wsrp:via>soap://B.com</wsrp:via>
<wsrp:via>soap://C.com</wsrp:via>
</wsrp:fwd>
<wsrp:from>soap://A.com/some/endpoint</wsrp:from>
<wsrp:id>uuid:84b9f5d0-33fb-4a81-b02b-5b760641c1d6</wsrp:id>
</wsrp:path>
</SOAP-ENV:Header>
<SOAP-ENV:Body>
...
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
与大多数 Web 服务规范一样,WS-Routing 可能会随着时间的推移而发展和/或与其他规范合并。我们在此提供了这个示例,作为 Web 服务社区在路由方面的发展方向的快照。 Like most Web services specifications, WS-Routing is likely to evolve over time and/or be merged with other specifications. We included the example here as a snapshot of where the Web services community is going with respect to routing. |
路由表演示了如何通过一系列动态处理步骤来路由消息。路由表的解决方案基于两个关键假设:必须预先确定处理步骤的顺序,并且该顺序是线性的。在许多情况下,这些假设可能无法实现。例如,可能必须根据中间结果做出路由决策。或者,处理步骤可能不是连续的,而是多个步骤可以并行执行。
The Routing Slip demonstrates how a message can be routed through a dynamic series of processing steps. The solution of the Routing Slip is based on two key assumptions: The sequence of processing steps has to be determined up front, and the sequence is linear. In many cases, these assumptions may not be fulfilled. For example, routing decisions might have to be made based on intermediate results. Or, the processing steps may not be sequential, but multiple steps might be executed in parallel.
|
当所需的步骤在设计时可能未知并且可能不连续时,我们如何通过多个处理步骤路由消息? How do we route a message through multiple processing steps when the required steps may not be known at design time and may not be sequential? |
管道和过滤器架构风格的主要优点之一是通过将各个处理单元(“过滤器”)与通道(“管道”)连接来将它们组合成一个序列。然后,每条消息都通过一系列处理单元(或组件)进行路由。如果我们需要能够更改每条消息的顺序,我们可以使用多个基于内容的路由器。该解决方案提供了最大的灵活性,但缺点是路由逻辑分布在许多路由组件中。路由表通过预先计算消息路径提供中心控制点,但不提供根据中间结果重新路由消息或同时执行多个步骤的灵活性。
One of the primary advantages of a Pipes and Filters architectural style is the composability of individual processing units ("filters") into a sequence by connecting them with channels ("pipes"). Each message is then routed through the sequence of processing units (or components). If we need to be able to change the sequence for each message, we can use multiple Content-Based Routers. This solution provides the maximum flexibility but has the disadvantage that the routing logic is spread across many routing components. The Routing Slip provides a central point of control by computing the message path up front, but does not provide the flexibility to reroute the message based on intermediate results or to execute multiple steps simultaneously.
如果在每个单独的处理单元之后,我们将控制权返回给中央组件,我们就可以获得灵活性并保持中央控制点。然后该组件可以确定下一个要执行的处理单元。按照这种方法,我们最终得到一个交替的处理流程:中央组件、处理单元、中央组件、处理单元等等。结果,中央单元在每个单独的处理步骤之后接收消息。当消息到达时,中央组件必须根据中间结果和序列中的当前步骤确定要执行的下一个处理步骤。这将需要各个处理单元向中央单元返回足够的信息来做出该决定。然而,这种方法将使处理单元依赖于中央单元的存在,因为它们可能必须传递与处理单元无关而仅与中央组件相关的无关信息。如果我们想要将各个处理步骤和关联的消息格式与中央单元解耦,我们需要为中央单元提供某种形式的“内存”,告诉它序列中最后执行的步骤。
We can gain flexibility and maintain a central point of control if, after each individual processing unit, we return control back to a central component. That component can then determine the next processing unit(s) to be executed. Following this approach, we end up with an alternating process flow: central component, processing unit, central component, processing unit, and so on. As a result, the central unit receives a message after each individual processing step. When the message arrives, the central component has to determine the next processing step(s) to be executed based on intermediate results and the current step in the sequence. This would require the individual processing units to return sufficient information to the central unit to make this decision. However, this approach would make the processing units dependent on the existence of the central unit because they might have to pass through extraneous information that is not relevant to the processing unit, but only to the central component. If we want to decouple the individual processing steps and the associated message formats from the central unit, we need to provide the central unit with some form of "memory" that tells it what step in the sequence was executed last.
|
使用中央处理单元(流程管理器)来维护序列的状态并根据中间结果确定下一个处理步骤。 Use a central processing unit, a Process Manager, to maintain the state of the sequence and determine the next processing step based on intermediate results. |
首先,让我们澄清一下,流程管理器的设计和配置是一个非常广泛的主题。我们可能可以用与工作流或业务流程管理设计相关的模式来写满整本书(也许是第 2 卷?)。因此,该模式的主要目的是“完善”路由模式的主题,并为工作流和流程建模的方向提供指导。它绝不是业务流程设计的综合处理。
First of all, let's clarify that the design and configuration of a Process Manager is a pretty extensive topic. We could probably fill a whole book (Volume 2, maybe?) with patterns related to the design of workflow or business process management. Therefore, this pattern is intended primarily to "round off" the topic of routing patterns and to provide a pointer into the direction of workflow and process modeling. By no means is it a comprehensive treatment of business process design.
使用流程管理器会产生所谓的中心辐射型消息流模式(参见图表)。传入消息将初始化流程管理器。我们将此消息称为触发消息。根据流程管理器内部的规则,它向由处理单元 A 实现的第一个处理步骤发送消息 (1)。单元 A 完成其任务后,将回复消息发送回流程。流程经理确定要执行的下一步并向下一个处理单元发送消息(2)。因此,所有消息流量都通过这个中央“中心”,因此称为“中心辐射”。这个中央控制元素的缺点是有将流程管理器变成性能瓶颈的危险。
Using a Process Manager results in a so-called hub-and-spoke pattern of message flow (see diagram). An incoming message initializes the Process Manager. We call this message the trigger message. Based on the rules inside the Process Manager, it sends a message (1) to the first processing step, implemented by Processing Unit A. After unit A completes its task, it sends a reply message back to the Process Manager. The Process Manager determines the next step to be executed and sends a message (2) to the next processing unit. As a result, all message traffic runs through this central "hub," hence the term hub-and-spoke. The downside of this central control element is the danger of turning the Process Manager into a performance bottleneck.
流程经理的多功能性同时也是其最大的优点和缺点。流程管理器可以执行任何顺序的步骤,顺序或并行。因此,几乎任何集成问题都可以通过流程管理器来解决。 同样,本章介绍的大多数模式都可以使用流程管理器来实现。事实上,许多EAI供应商让你相信每个集成问题都是一个流程问题。我们认为在所有情况下都使用流程管理器可能有点过分了。它可能会分散核心设计问题的注意力,还会导致显着的性能开销。
The versatility of a Process Manager is at the same time its biggest strength and weakness. A Process Manager can execute any sequence of steps, sequential or in parallel. Therefore, almost any integration problem can be solved with a Process Manager. Likewise, most of the patterns introduced in this chapter could be implemented using a Process Manager. In fact, many EAI vendors make you believe that every integration problem is a process problem. We think that using a Process Manager for every situation may be overkill. It can distract from the core design issue and also cause significant performance overhead.
流程管理器的关键功能之一是维护消息之间的状态。例如,当第二处理单元向流程管理器返回消息时,流程管理器需要记住这是许多步骤序列中的步骤2。我们不想将这些知识与处理单元联系起来,因为同一单元可能会在同一进程中出现多次。例如,处理单元B可以是单个过程的步骤2和步骤4。结果,处理单元B发送的相同回复消息可以触发流程管理器基于流程上下文执行步骤3或步骤5。为了在不使处理单元复杂化的情况下实现这一点,流程管理器需要维护流程执行中的当前位置。
One of the key functions of the Process Manager is to maintain state between messages. For example, when the second processing unit returns a message to the Process Manager, the Process Manager needs to remember that this is step 2 out of a sequence of many steps. We do not want to tie this knowledge to the processing unit because the same unit may appear multiple times inside the same process. For example, Processing Unit B may be both step 2 and step 4 of a single process. As a result, the same reply message sent by Processing Unit B may trigger the Process Manager to execute step 3 or step 5 based on the process context. To accomplish this without complicating the processing unit, the Process Manager needs to maintain the current position in the process execution.
除了流程中的当前位置之外,流程管理器能够存储其他信息非常有用。如果与后续步骤相关,流程管理器可以存储先前处理的中间结果。例如,如果步骤 1 的结果与后面的步骤相关,则流程管理器可以存储此信息,而不会因来回传递此数据而增加后续处理单元的负担。这允许各个处理步骤彼此独立,因为它们不必担心其他单元生成或消耗的数据。实际上,流程经理扮演着索赔检查的角色稍后解释。
It is useful for the Process Manager to be able to store additional information besides the current position in the process. The Process Manager can store intermediate results from previous processing if it is relevant to later steps. For example, if the results of step 1 are relevant to a later step, the Process Manager can store this information without burdening the subsequent processing units with passing this data back and forth. This allows the individual processing steps to be independent of each other because they do not have to worry about data produced or consumed by other units. Effectively, the Process Manager plays the role of a Claim Check explained later.
由于流程执行可能跨越许多步骤,因此可能需要很长时间,因此流程管理器需要准备好在另一个流程仍在执行时接收新的触发消息。为了管理多个并行执行,流程管理器为每个传入的触发消息创建一个新的流程实例。流程实例存储与由触发消息启动的流程的执行相关联的状态。状态包括流程的当前执行步骤和任何相关数据。每个流程实例都由唯一的流程标识符来标识。
Because the process execution can span many steps and can therefore take a long time, the Process Manager needs to be prepared to receive new trigger messages while another process is still executing. In order to manage multiple parallel executions, the Process Manager creates a new process instance for each incoming trigger message. The process instance stores the state associated with the execution of the process initiated by the trigger message. The state includes the current execution step of the process and any associated data. Each process instance is identified by a unique process identifier.
区分流程定义(也称为流程模板)和流程实例的概念非常重要。流程定义是一种设计构造,它定义了要执行的步骤序列,类似于面向对象语言中的类。流程实例是特定模板的主动执行,类似于 OO 语言中的对象实例。下图显示了一个简单示例,其中包含一个流程定义和两个流程实例。第一个实例(进程标识符 1234)当前正在执行步骤 1,而第二个进程实例(进程标识符 5678)正在并行执行步骤 2 和步骤 5。
It is important to separate the concepts of a process definition (also referred to as process template) and a process instance. The process definition is a design construct that defines the sequence of steps to be executed, comparable to a class in object-oriented languages. The process instance is an active execution of a specific template, comparable to an object instance in an OO language. The diagram below shows a simple example with one process definition and two process instances. The first instance (process identifier 1234) is currently executing step 1, while the second process instance (process identifier 5678) is executing steps 2 and 5 in parallel.
基于一个流程定义的多个流程实例
Multiple Process Instances Based on One Process Definition
由于多个流程实例可能同时执行,因此流程管理器需要能够将传入消息与正确的实例关联起来。例如,如果前面示例中的流程管理器从处理单元接收到一条消息,那么该消息是针对哪个流程实例的?多个实例可能正在执行同一步骤,因此流程管理器无法从通道名称或消息类型派生实例。将传入消息与流程实例关联起来的要求让我们想起了关联标识符。相关标识符允许组件通过在回复消息中存储与请求消息相关联的唯一标识符来将传入回复消息与原始请求相关联。使用此标识符,即使组件发送了多个请求并且回复未按顺序到达,组件也可以将回复与正确的请求相匹配。流程管理器需要类似的机制。当流程管理器从处理单元接收到消息时,它必须能够将该消息与将该消息发送到该处理单元的流程实例相关联。流程管理器必须包含关联标识符它发送到处理单元的内部消息。该组件需要在回复消息中返回该标识符作为Correlation Identifier 。如果每个流程实例维护唯一的流程标识符,则它可以使用该标识符作为消息的关联标识符。
Because multiple process instances may be executing simultaneously, the Process Manager needs to be able to associate an incoming message with the correct instance. For example, if the Process Manager in the previous example receives a message from a processing unit, which process instance is the message meant for? Multiple instances may be executing the same step, so the Process Manager cannot derive the instance from the channel name or the type of message. The requirement to associate an incoming message with a process instance reminds us of the Correlation Identifier. The Correlation Identifier allows a component to associate an incoming reply message with the original request by storing a unique identifier in the reply message that correlates it to the request message. Using this identifier, the component can match the reply with the correct request even if the component has sent multiple requests and the replies arrive out of order. The Process Manager requires a similar mechanism. When the Process Manager receives a message from a processing unit, it must be able to associate the message with the process instance that sent the message to the processing unit. The Process Manager must include a Correlation Identifier inside messages that it sends to processing units. The component needs to return this identifier in the reply message as a Correlation Identifier. If each process instance maintains a unique process identifier, it can use that identifier as a Correlation Identifier for messages.
显然,状态管理是流程管理器的一个重要特性。那么,以前的模式是如何在不管理状态的情况下摆脱困境的呢?在传统的管道和过滤器架构中,管道(即消息通道)管理状态。继续前面的示例,如果我们要使用通过消息通道连接的硬连线组件来实现该流程,它看起来如下图所示。如果我们假设该系统与前面的示例处于相同的状态(即,有两个流程实例),则它相当于一条标识符为 1234 的消息位于通道中等待组件 1 处理,另外两条消息的标识符为 1234 5678分别等待组件2和5处理。一旦组件 1 使用该消息并完成其处理任务,它就会向组件 2 和 4 广播一条新消息,这与上一示例中的流程管理器的行为完全相同。
It is apparent that state management is an important feature of the Process Manager. How, then, did the previous patterns get away without managing state? In a traditional Pipes and Filters architecture, the pipes (i.e., the Message Channels ) manage the state. To continue the previous example, if we were to implement the process with hard-wired components connected by Message Channels, it would look like the following figure. If we assume that this system is in the same state as the previous example (i.e., has two process instances), it equates to one message with the identifier 1234 sitting in a channel waiting to be processed by component 1 and two messages with the identifier 5678 waiting to be processed by the components 2 and 5 respectively. As soon as component 1 consumes the message and completes its processing tasks, it broadcasts a new message to components 2 and 4exactly the same behavior as the Process Manager in the previous example.
保持通道状态
Keeping State in Channels
令人惊讶的是,此示例中使用的消息流表示法与 UML 活动图非常相似,而 UML 活动图通常用于对Process Manager组件的行为进行建模。实际上,我们可以在设计过程中使用抽象符号对系统的行为进行建模,然后决定是否要将行为实现为分布式管道和过滤器架构,还是使用中央流程管理器的中心辐射型架构。尽管本书没有足够的篇幅来深入探讨流程模型的设计,但这种语言中的许多模式在设计流程模型时确实适用。
It is striking how much the message flow notation used for this example resembles a UML activity diagram that is often used to model the behavior of Process Manager components. Effectively, we can use an abstract notation to model the behavior of the system during design and then decide whether we want to implement the behavior as a distributed Pipes and Filters architecture or as a hub-and-spoke architecture using a central Process Manager. Even though we don't have room in this book to dive too deeply into the design of process models, many of the patterns in this language do apply when designing a process model.
与大多数架构决策一样,实施中央流程管理器或分布式管道和过滤器架构并不是简单的是或否的决定。在许多情况下,使用多个Process Manager组件是最有意义的,每个组件都包含较大流程的特定方面。然后,流程管理器组件可以通过管道和过滤器架构相互通信。
As with most architectural decisions, implementing a central Process Manager or a distributed Pipes and Filters architecture is not a simple yes or no decision. In many cases, it makes most sense to use multiple Process Manager components, each of which houses a particular aspect of a larger process. The Process Manager components can then communicate with each other through a Pipes and Filters architecture.
在流程管理器内显式管理状态可能需要更复杂的组件,但它允许更强大的流程报告。例如,流程管理器的大多数实现都提供查询流程实例状态的能力。这样可以轻松查看当前有多少订单正在等待批准或因库存不足而被搁置。我们还可以告诉每个客户他或她的订单状态。如果我们使用硬连线通道,我们将必须检查所有通道以获得相同的信息。进程管理器的这个属性不仅对于报告很重要,而且对于调试也很重要。使用中央流程管理器可以轻松检索进程的当前状态和关联数据。调试完全分布式的体系结构可能会更具挑战性,并且如果没有消息历史记录或消息存储等机制的帮助,几乎是不可能的。
Managing state explicitly inside a Process Manager may require a more complex component, but it allows much more powerful process reporting. For example, most implementations of a Process Manager provide the ability to query process instance state. This makes it easy to see how many orders are currently waiting for approval or have been put on hold because of lacking inventory. We can also tell each customer the status of his or her order. If we used hard-wired channels, we would have to inspect all channels to obtain the same information. This property of the Process Manager is not only important for reporting, but also for debugging. Using a central Process Manager makes it easy to retrieve the current state of a process and the associated data. Debugging a fully distributed architecture can be a lot more challenging and is almost impossible without the assistance of such mechanisms as the Message History or Message Store.
大多数商业 EAI 实施都包括流程管理器组件和可视化工具,用于对流程定义进行建模。大多数可视化工具使用类似于 UML 活动图的表示法,因为流程管理器的语义和活动图非常相似。此外,活动图是并行执行的多个任务的良好可视化表示。直到最近,大多数供应商工具都将视觉符号转换为供应商专有的内部流程定义,以由流程引擎执行。然而,在 Web 服务的保护下推动分布式系统各个方面的标准化并没有忽视流程定义的重要作用。这些努力的结果出现了三种拟议的“语言”。Microsoft 定义了 XLANG,并得到其 BizTalk 编排建模工具系列的支持。IBM 起草了 WSFL,即 Web 服务流语言 [ WSFL]。最近,两家公司联手创建了 BPEL4WS 规范,即 Web 服务的业务流程执行语言(请参阅 [ BPEL4WS ])。BPEL4WS 是一种功能强大的语言,它将流程模型描述为 XML 文档。目的是定义流程建模工具和流程管理器引擎之间的标准化中间语言。这样,我们可以使用供应商 X 的产品对我们的流程进行建模,并决定在供应商 Y 的流程引擎实现上执行该流程。有关 Web 服务标准对集成的影响的更多信息,请参阅第 14章中的“企业集成中的新兴标准和未来”。
Most commercial EAI implementations include a Process Manager component combined with visual tools to model the process definition. Most visual tools use a notation that resembles UML activity diagrams because the semantics of a Process Manager and those of an activity diagram are fairly similar. Also, activity diagrams are a good visual representation of multiple tasks executing in parallel. Until recently, most vendor tools converted the visual notation into an internal, vendor-proprietary process definition to be executed by the process engine. However, the push to standardize various aspects of distributed systems under the umbrella of Web services has not ignored the important role of process definitions. Three proposed "languages" have emerged as a result of these efforts. Microsoft defined XLANG, which is supported by its family of BizTalk orchestration modeling tools. IBM drafted the WSFL, the Web Services Flow Language [WSFL]. Recently, both companies have joined forces to create the specification for BPEL4WS, the Business Process Execution Language for Web Services (see [BPEL4WS]). The BPEL4WS is a powerful language that describes a process model as an XML document. The intent is to define a standardized intermediate language between the process modeling tools and the Process Manager engines. This way, we could model our processes with Vendor X's product and decide to execute the process on Vendor Y's process engine implementation. For more information on the impact of Web services standards on integration, see "Emerging Standards and Futures in Enterprise Integration" in Chapter 14.
流程定义的语义可以用相当简单的术语来描述。基本构建块是活动(有时称为任务或操作)。通常,活动可以向另一个组件发送消息、等待传入消息或在内部执行特定功能(例如,消息转换器)。活动可以以串行方式连接,也可以使用 fork 和 join 构造并行执行多个活动。分叉允许同时执行多个活动。它在语义上相当于硬连线管道和过滤器中的发布-订阅通道建筑学。连接将多个并行执行线程同步回单个线程。仅当所有并行线程都完成了各自的活动时,连接后的执行才能继续。在管道和过滤器风格中,聚合器通常用于此目的。流程模板还必须能够指定分支或决策点,以便执行路径可以根据消息字段的内容进行更改。这个功能相当于一个Content-Based Router。许多建模工具都包含设计循环构造的功能,但这实际上是分支的一个特例。下图突出显示了流程定义(描述为 UML 活动图)与使用此模式语言中定义的模式的管道和过滤器实现之间的语义相似性,尽管物理实现非常不同。
The semantics of a process definition can be described in rather simple terms. The basic building block is an activity (sometimes called task or action). Usually, an activity can send a message to another component, wait for an incoming message, or execute a specific function internally (e.g., a Message Translator ). Activities can be connected in serial fashion, or multiple activities can be executed in parallel using a fork and join construct. A fork allows multiple activities to execute at the same time. It is semantically equivalent to a Publish-Subscribe Channel in a hard-wired Pipes and Filters architecture. A join synchronizes multiple parallel threads of execution back into a single thread. Execution after a join can continue only if all parallel threads have completed their respective activities. In the Pipes and Filters style, an Aggregator often serves this purpose. The process template also must be able to specify a branch, or decision point, so that the path of execution can change based on the content of a message field. This function is equivalent to a Content-Based Router. Many modeling tools include the ability to design a loop construct, but this is really a special case of the branch. The following figure highlights the semantic similarities between a process definition (depicted as a UML activity diagram) and a Pipes and Filters implementation using the patterns defined in this pattern language, even though the physical implementations are very different.
示例 UML 活动图和相应的管道和过滤器实现
Example UML Activity Diagram and Corresponding Pipes-and-Filters Implementation
我们已经多次对比了基本的管道和过滤器架构、路由表和流程管理器。我们将这些模式之间的主要差异整理到下表中,以突出显示选择正确架构时涉及的权衡。
We have contrasted a basic Pipes and Filters architecture, the Routing Slip, and the Process Manager several times. We compiled the key differences between these patterns into the following table to highlight the trade-offs involved in choosing the correct architecture.
分布式管道和过滤器 Distributed Pipes and Filters | 路由表 Routing Slip | 中央流程管理器 Central Process Manager |
|---|---|---|
支持复杂的消息流 Supports complex message flow | 仅支持简单的线性流 Supports only simple, linear flow | 支持复杂的消息流 Supports complex message flow |
改变流量困难 Difficult to change flow | 易于改变流量 Easy to change flow | 易于改变流量 Easy to change flow |
无中心故障点 No central point of failure | 潜在故障点(计算路由表) Potential point of failure (compute routing table) | 潜在的故障点 Potential point of failure |
高效的分布式运行时架构 Efficient distributed runtime architecture | 大部分是分布式的 Mostly distributed | 中心辐射型架构可能会导致瓶颈 Hub-and-spoke architecture may lead to bottleneck |
没有管理和报告的中心点 No central point of administration and reporting | 管理中心点,但不报告 Central point of administration, but not reporting | 管理和报告的中心点 Central point of administration and reporting |
控制和状态管理的中心点也可能意味着故障中心点或性能瓶颈。因此,大多数流程管理器实现都允许在文件或数据库中持久存储流程实例状态。然后,该实现可以利用通常在企业级数据库系统中实现的冗余数据存储机制。并行运行多个Process Manager也很常见。并行进程管理器通常很容易,因为流程实例彼此独立。这允许我们跨多个流程引擎分发流程实例。如果流程引擎将所有状态信息保存在共享数据库中,则系统可以变得足够健壮,能够在流程引擎发生故障时继续存在,而另一个引擎可以简单地从前一个引擎停止的地方继续工作。这种方法的缺点是每个处理步骤之后每个流程实例的状态都必须保存在中央数据库中。这很容易将数据库变成新的性能瓶颈。正如经常发生的那样,架构师必须在性能、稳健性、成本和可维护性之间找到正确的平衡。
A central point of control and state management can also mean a central point of failure or a performance bottleneck. For this reason, most Process Manager implementations allow persistent storage of process instance state in a file or in a database. The implementation can then leverage redundant data storage mechanisms typically implemented in enterprise-class database systems. It is also common to run multiple Process Managers in parallel. Parallelizing Process Managers is generally easy because process instances are independent from each other. This allows us to distribute process instances across multiple process engines. If the process engine persists all state information in a shared database, the system can become robust enough to survive the failure of a process engineanother engine can simply pick up where the previous one left off. The downside of this approach is that the state of each process instance has to be persisted in a central database after each processing step. This could easily turn the database into a new performance bottleneck. As so often happens, the architect has to find the correct balance between performance, robustness, cost, and maintainability.
|
示例: 贷款经纪人 Example: Loan Broker 贷款经纪人示例的 MSMQ 实现(请参阅第 9 章中的“ MSMQ 异步实现” )实现了一个简单的流程管理器。该示例通过为流程管理器和流程实例编写 C# 类,从头开始创建流程管理器功能。同一示例的 TIBCO 实现(请参阅第 9章中的“使用 TIBCO ActiveEnterprise 进行异步实现” )使用商业流程管理工具。 The MSMQ implementation of the Loan Broker example (see "Asynchronous Implementation with MSMQ" in Chapter 9) implements a simple Process Manager. The example creates the Process Manager functionality from scratch by coding C# classes for both the process manager and process instances. The TIBCO implementation of the same example (see "Asynchronous Implementation with TIBCO ActiveEnterprise" in Chapter 9) uses a commercial process management tool. |
|
示例: Microsoft BizTalk 编排管理器 Example: Microsoft BizTalk Orchestration Manager 大多数商业 EAI 工具都包含流程设计和执行功能。例如,Microsoft BizTalk 允许用户通过集成到 Visual Studio .NET 编程环境中的 Orchestration Designer 工具来设计流程定义。 Most commercial EAI tools include process design and execution capabilities. For example, Microsoft BizTalk lets users design process definitions via the Orchestration Designer tool that is integrated into the Visual Studio .NET programming environment. Microsoft BizTalk 2004 编排设计器 Microsoft BizTalk 2004 Orchestration Designer 这个简单的示例编排接收订单消息并执行两个并行活动。一个活动创建到库存系统的请求消息,另一个活动创建到信用系统的请求消息。一旦收到两个响应,该过程就会继续。视觉符号使遵循流程定义变得容易。 This simple example orchestration receives an order message and executes two parallel activities. One activity creates a request message to the inventory systems, and the other activity creates a request message to the credit system. Once both responses are received, the process continues. The visual notation makes it easy to follow the process definition. |
本章中的许多模式提供了将消息路由到正确目的地的方法,而原始应用程序不知道消息的最终目的地。大多数模式都专注于特定类型的路由逻辑。然而,总的来说,这些模式解决了一个更大的问题。
Many patterns in this chapter present ways to route messages to the proper destination without the originating application being aware of the ultimate destination of the message. Most of the patterns focus on specific types of routing logic. However, in aggregate, these patterns solve a bigger problem.
|
如何将消息的目的地与发送者分离并保持对消息流的集中控制? How can you decouple the destination of a message from the sender and maintain central control over the flow of messages? |
使用简单的消息通道已经在发送者和接收者之间提供了一定程度的间接性——发送者只知道通道的信息,而不知道接收者的信息。然而,如果每个接收器都有自己的通道,那么这种间接级别就变得没有意义。发送方必须知道与接收方关联的正确通道名称,而不是知道接收方的地址。
Using a simple Message Channel already provides a level of indirection between sender and receiverthe sender knows only about the channel but not about the receiver. However, if each receiver has its own channel, this level of indirection becomes less meaningful. Instead of knowing the receiver's address, the sender has to know the correct channel name that is associated with the receiver.
除了最简单的消息传递解决方案之外,所有解决方案都连接许多不同的应用程序。如果我们创建单独的消息通道来将每个应用程序连接到其他应用程序,系统中的通道数量将很快激增到难以管理的数量,从而导致集成意大利面条(见图)。
All but the most trivial messaging solutions connect a number of different applications. If we created individual message channels to connect each application to each other application, the channels in the system would quickly explode into an unmanageable number, resulting in integration spaghetti (see figure).
点对点连接造成的集成意大利面条
Integration Spaghetti as a Result of Point-to-Point Connections
该图说明了各个应用程序之间的直接通道可能会导致通道数量激增,并首先减少通过通道路由消息的许多好处。这些类型的集成架构通常是随着时间的推移不断发展的解决方案的结果。首先,客户服务系统必须与会计系统对话。然后,客户服务系统还需要从库存系统中检索信息,而运输系统则需要使用运输费用来更新会计系统。很容易看出“再添加一件”会如何迅速损害解决方案的整体完整性。
This diagram illustrates that direct channels between individual applications can lead to an explosion of the number of channels and reduce many of the benefits of routing messages through channels in the first place. These types of integration architectures are often a result of a solution that grew over time. First, the customer care system had to talk to the accounting system. Then, the customer care system was also expected to retrieve information from the inventory system, and the shipping system was to update the accounting system with the shipping charges. It is easy to see how "adding one more piece" can quickly compromise the overall integrity of solution.
要求应用程序与所有其他应用程序显式通信可能会很快妨碍系统的可维护性。例如,如果客户服务系统中的客户地址发生变化,则该系统必须向维护客户地址副本的所有系统发送消息。每次添加新系统时,客户服务系统都必须知道该系统是否使用地址并进行相应的更改。
Requiring an application to explicitly communicate with all other applications can quickly hamper the maintainability of the system. For example, if the customer address changes in the customer care system, this system would have to send a message to all systems that maintain copies of the customer address. Every time a new system is added, the customer care system would have to know whether that system uses addresses and be changed accordingly.
发布-订阅通道提供这适用于简单的广播场景,但路由规则通常要复杂得多。例如,传入的订单消息可能必须根据订单的大小或性质路由到不同的系统。为了避免让应用程序负责确定消息的最终目的地,中间件应该包括一个可以将消息路由到适当目的地的消息路由器。
Publish-Subscribe Channels provide some form of basic routingthe message is routed to each application that subscribed to the specific channel. This works in simple broadcast scenarios, but routing rules often are much more complicated. For example, an incoming order message may have to be routed to a different system based on the size or the nature of the order. To avoid making the applications responsible for determining a message's ultimate destination, the middleware should include a Message Router that can route messages to the appropriate destination.
单独的消息路由模式帮助我们将发送者与接收者解耦。例如,收件人列表可以帮助将有关所有收件人的信息从发件人处提取到中间件层。将逻辑移至中间件层对我们有两个帮助。首先,许多商业中间件和 EAI 套件提供专门用于执行此类任务的工具和库。这简化了编码工作,因为我们不必编写消息端点相关的代码,例如事件驱动的消费者或线程管理。此外,在中间件层内部实现逻辑使我们能够使逻辑比应用程序内部的实际逻辑“更智能”。例如,当新系统添加到集成解决方案时,使用动态收件人列表可以避免编码更改。
Individual message routing patterns have helped us decouple the sender from the receiver(s). For example, a Recipient List can help pull the knowledge about all recipients out of the sender and into the middleware layer. Moving the logic into the middleware layer helps us in two ways. First, many of the commercial middleware and EAI suites provide tools and libraries that are specialized to perform these kinds of tasks. This simplifies the coding effort because we do not have to write the Message Endpoint related code, such as Event-Driven Consumers or thread management. Also, implementing the logic inside the middleware layer allows us to make the logic "smarter" than would be practical inside of the application. For example, using a dynamic Recipient List can avoid coding changes when new systems are added to the integration solution.
然而,拥有大量单独的消息路由器组件几乎与我们试图解决的集成意大利面条一样难以管理。
However, having a large number of individual Message Router components can be almost as hard to manage as the integration spaghetti we were trying to resolve.
|
使用中央消息代理,它可以从多个目标接收消息、确定正确的目标并将消息路由到正确的通道。使用其他消息路由器实现消息代理的内部结构。 Use a central Message Broker that can receive messages from multiple destinations, determine the correct destination, and route the message to the correct channel. Implement the internals of the Message Broker using other message routers. |
使用中央消息代理有时被称为中心辐射型架构风格,从上图来看,这似乎是一个描述性名称。
Using a central Message Broker is sometimes referred to as hub-and-spoke architectural style, which appears to be a descriptive name when looking at the figure above.
消息代理模式的范围与本章中介绍的大多数其他模式略有不同。它是一种架构模式,而不是单独的设计模式。因此,它类似于管道和过滤器架构风格,它为我们提供了一种将组件链接在一起以形成更复杂消息流的基本方法。消息代理不仅仅是链接各个组件,它还关注更大的解决方案,并帮助我们处理管理此类系统不可避免的复杂性。
The Message Broker pattern has a slightly different scope than most of the other patterns presented in this chapter. It is an architecture pattern as opposed to individual design pattern. As such, it is comparable to the Pipes and Filters architectural style, which gives us a fundamental way of chaining components together to form more complex message flows. Rather than just chaining individual components, the Message Broker concerns itself with larger solutions and helps us deal with the inevitable complexity of managing such a system.
Message Broker不是一个整体组件。在内部,它使用了本章中介绍的许多消息路由模式。因此,一旦您决定使用Message Broker作为架构模式,您就可以选择正确的Message Router设计模式来实现Message Broker。
The Message Broker is not a monolithic component. Internally, it uses many of the message routing patterns presented in this chapter. So, once you decide to use the Message Broker as an architectural pattern, you can choose the correct Message Router design patterns to implement the Message Broker.
消息代理集中维护的优点也可能变成缺点。通过单个Message Broker路由所有消息可能会将Message Broker变成严重的瓶颈。许多技术可以帮助我们缓解这个问题。例如,消息代理模式只告诉我们开发一个执行路由的实体。它没有规定我们在部署时在系统中部署多少个该实体的实例。如果消息代理设计是无状态的(即,如果它仅由无状态组件组成),我们可以轻松部署代理的多个实例以提高吞吐量。点对点通道的属性可确保只有一个Message Broker 实例使用任何传入消息。此外,与大多数现实生活中的情况一样,最终的解决方案最终是模式的组合。同样,在许多复杂的集成解决方案中,设计多个Message Broker组件可能是有意义的,每个组件专门负责解决方案的特定部分。这可以避免创建 uber- Message Broker这太复杂了,以至于无法维护。明显的另一面是,我们不再有单点维护,并且可以创建一种新形式的Message Broker意大利面。一种使用Message Broker组合的优秀架构风格是Message Broker 层次结构(见图)。此配置类似于由各个子网组成的网络配置。如果消息仅需要在子网内的两个应用程序之间传输,则本地Message Broker可以管理消息的路由。如果消息的目的地是另一个子网,则本地Message Broker可以将消息传递到中央Message Broker,然后决定最终目的地。中央Message Broker执行与本地Message Broker相同的功能,但它不是解耦单个应用程序,而是解耦由多个应用程序组成的整个子系统。
The advantage of central maintenance of a Message Broker can also turn into a disadvantage. Routing all messages through a single Message Broker can turn the Message Broker into a serious bottleneck. A number of techniques can help us alleviate this problem. For example, the Message Broker pattern only tells us to develop a single entity that performs routing. It does not prescribe how many instances of this entity we deploy in the system at deployment time. If the Message Broker design is stateless (i.e., if it is composed only of stateless components), we can easily deploy multiple instances of the broker to improve throughput. The properties of a Point-to-Point Channel ensure that only one instance of the Message Broker consumes any incoming message. Also, as in most real-life situations, the ultimate solution ends up being a combination of patterns. Likewise, in many complex integration solutions, it may make sense to design multiple Message Broker components, each specializing in a specific portion of the solution. This avoids creating the über-Message Broker that is so complex as to become unmaintainable. The apparent flip-side is that we no longer have a single point of maintenance and could create a new form of Message Broker spaghetti. One excellent architectural style that uses a combination of Message Brokers is a Message Broker hierarchy (see figure). This configuration resembles a network configuration composed out of individual subnets. If a message has to travel only between two applications inside a subnet, the local Message Broker can manage the routing of the message. If the message is destined for another subnet, the local Message Broker can pass the message to the central Message Broker, which then determines the ultimate destination. The central Message Broker performs the same functions as a local Message Broker, but instead of decoupling individual applications, it decouples whole subsystems consisting of multiple applications.
消息代理的层次结构提供了解耦,同时避免了超级代理
A Hierarchy of Message Brokers Provides Decoupling While Avoiding the Über-Broker
由于消息代理的目的是减少各个应用程序之间的耦合,因此它通常必须处理应用程序之间的消息数据格式转换。如果发送应用程序必须以(假定隐藏的)目的地的消息格式来格式化消息,那么让消息代理抽象消息的路由对发送应用程序没有任何帮助。下一章将介绍一系列消息转换模式来解决这些问题。在许多情况下,消息代理在内部使用规范数据模型来避免N-平方问题(系统中每个接收者之间进行翻译所需的翻译人员数量随着参与者数量的平方而增长)。
Because the purpose of the Message Broker is to reduce coupling between individual applications, it usually has to deal with translating message data formats between applications. Having a Message Broker abstract the routing of the message does not help the sending application if it has to format the message in the (supposedly hidden) destination's message format. The next chapter introduces a series of message transformation patterns to address these issues. In many cases, a Message Broker uses a Canonical Data Model internally to avoid the N-square problem (the number of translators required to translate between each and every recipient in a system grows with the square of the number of participants).
|
示例: 商业 EAI 工具 Example: Commercial EAI Tools 大多数商业 EAI 套件提供的工具可以大大简化集成解决方案的Message Broker组件的创建。这些工具套件通常提供许多支持Message Broker的开发和部署的功能: Most commercial EAI suites provide tools to greatly simplify the creation of Message Broker components for integration solutions. These tool suites typically provide a number of features that support the development and deployment of Message Brokers:
|
正如消息转换器中所述,需要由消息传递系统集成的应用程序很少就通用数据格式达成一致。例如,会计系统的客户对象概念与客户关系管理系统不同。最重要的是,一个系统可以将数据保存在关系模型中,而另一个应用程序则使用平面文件或 XML 文档。集成现有应用程序通常意味着我们没有修改应用程序以更轻松地与其他系统配合使用的自由。相反,集成解决方案必须适应并解决不同系统之间的差异。消息翻译器模式为数据格式的这种差异提供了通用的解决方案。本章探讨消息转换器的特定变体。
As described in the Message Translator, applications that need to be integrated by a messaging system rarely agree on a common data format. For example, an accounting system will have a different notion of a Customer object than will a customer relationship management system. On top of that, one system may persist data in a relational model, while another application uses flat files or XML documents. Integrating existing applications often means that we do not have the liberty of modifying the applications to work more easily with other systems. Rather, the integration solution has to accommodate and resolve the differences between the varying systems. The Message Translator pattern offers a general solution to such differences in data formats. This chapter explores specific variants of the Message Translator.
大多数消息系统对消息头的格式和内容都有特定的要求。我们将消息有效负载数据包装到符合消息传递基础设施要求的信封包装器中。如果消息跨不同的消息基础设施传递,则可以组合多个信封包装器。
Most messaging systems place specific requirements on the format and contents of a message header. We wrap message payload data into an Envelope Wrapper that is compliant with the requirements of the messaging infrastructure. Multiple Envelope Wrappers can be combined if a message is passed across different messaging infrastructures.
如果目标系统需要原始系统无法提供的数据字段,则需要内容丰富器。它能够查找缺失的信息或根据可用数据进行计算。内容过滤器则相反,它会从邮件中删除不需要的数据。声明检查还会从消息中删除数据,但将其存储起来以供以后检索。规范化器将以多种不同格式到达的消息转换为通用格式。
A Content Enricher is needed if the target system requires data fields that the originating system cannot supply. It has the ability to look up missing information or compute it from the available data. The Content Filter does the oppositeit removes unwanted data from a message. The Claim Check also removes data from a message but stores it for later retrieval. The Normalizer translates messages arriving in many different formats into a common format.
消息转换是集成中的一个深层次主题。消息通道和消息路由器可以消除一个应用程序了解另一个应用程序位置的需要,从而消除应用程序之间的基本依赖关系。
Message transformation is a deep topic in integration. Message Channels and Message Routers can remove basic dependencies between applications by eliminating the need for one application to be aware of the other's location.
一个应用程序可以将消息发送到消息通道,而不必担心哪个应用程序将使用它。然而,消息格式强加了另一组依赖性。如果一个应用程序必须将消息格式化为另一个应用程序的数据格式,那么消息通道形式的解耦在某种程度上是一种幻想。对接收应用程序的任何更改或从一个接收应用程序切换到另一接收应用程序仍然需要对发送应用程序进行更改。消息翻译器有助于消除这种依赖性。
One application can send a message to a Message Channel and not worry about what application will consume it. However, message formats impose another set of dependencies. If one application has to format messages in another application's data format, the decoupling in the form of the Message Channel is somewhat of an illusion. Any change to the receiving application or the switch from one receiving application to another still requires a change to the sending application. Message Translators help remove this dependency.
将消息从一种格式转换为另一种格式需要我们处理描述实际数据格式的元数据。虽然从一个应用程序发送到另一个应用程序的消息可能会告诉我们 ID 为 123 的客户从加利福尼亚州旧金山搬到北卡罗来纳州罗利,但关联的元数据可能会告诉我们此地址更改消息使用数字客户 ID 字段并存储客户的名字和姓氏位于两个文本字段中,每个字段最多 40 个字符。
Transforming messages from one format into another requires us to deal with metadatadata that describes the format of actual data. While a message from one application to another may tell us that the customer with the ID 123 moved from San Francisco, California, to Raleigh, North Carolina, the associated metadata may tell us that this Address Change message uses a numeric customer ID field and stores the first and last names of the customer in two text fields of up to 40 characters each.
元数据在集成中扮演着如此重要的角色,我们可以将大多数集成解决方案视为两个并行系统之间的相互作用。一个处理实际的消息数据,另一个处理元数据。许多用于设计消息数据流的模式也可用于管理元数据流。例如,通道适配器不仅可以将消息移入和移出系统,还可以从外部应用程序提取元数据并将其加载到中央元数据存储库中。使用此存储库,集成开发人员可以定义应用程序元数据和规范数据模型之间的转换。
Metadata plays such an important role in integration that we can view most integration solutions as interplay between to two parallel systems. One deals with actual message data, the other with metadata. Many of the patterns used to design the flow of message data can also be used to manage the flow of metadata. For example, a Channel Adapter not only can move messages in and out of a system, but can also extract metadata from external applications and load it into a central metadata repository. Using this repository, the integration developers can define transformations between the application metadata and the Canonical Data Model.
元数据集成
Metadata Integration
例如,上图描述了两个需要交换客户信息的应用程序之间的集成。每个系统对客户数据的定义略有不同。应用程序 A 将名字和姓氏存储在两个单独的字段中,而应用程序 B 将它们存储在一个字段中。同样,应用程序 A 存储客户的邮政编码而不是州,而应用程序 B 只存储州缩写。从应用程序 A 流向应用程序 B 的消息必须经过转换,以便应用程序 B 可以接收所需格式的数据。如果通道适配器还可以提取元数据(例如,描述消息格式的数据)。然后可以将该元数据加载到存储库中,从而大大简化消息转换器的配置和验证。 元数据可以以多种格式存储。用于 XML 消息的常见格式是 XSDeXtensible Schema Definition。其他 EAI 工具实现专有的元数据格式,但允许管理员将元数据导入和导出为不同的格式。
For example, the previous figure depicts the integration between two applications that need to exchange customer information. Each system has a slightly different definition of customer data. Application A stores first and last names in two separate fields, whereas Application B stores them in one field. Likewise, Application A stores the customer's ZIP code and not the state, while Application B stores only the state abbreviation. Messages flowing from Application A to Application B have to undergo a transformation so that Application B can receive data in the required format. Creating the transformation is much simplified if the Channel Adapters can also extract metadata (e.g., data describing the message format). This metadata can then be loaded into a repository, greatly simplifying the configuration and validation of the Message Translator. The metadata can be stored in a variety of formats. A common format used for XML messages is XSDeXtensible Schema Definition. Other EAI tools implement proprietary metadata formats but allow administrators to import and export metadata into different formats.
这些转换模式中包含的许多原则也适用于基于消息的集成之外。例如,文件传输必须执行系统之间的转换功能。同样,远程过程调用即使应用程序的内部格式不同,也必须以要调用的服务指定的数据格式发出请求。这通常需要调用应用程序执行数据转换。一些最复杂的转换引擎已集成到 ETL(提取、转换、加载)工具中,例如 Informatica 或 DataMirror。这些工具通常会立即转换大量数据,而不是转换单个消息。
Many of the principles incorporated in these transformation patterns are applicable outside of message-based integration. For example, File Transfer has to perform transformation functions between systems. Likewise, Remote Procedure Invocation has to make requests in the data format specified by the service that is to be called even if the application's internal format is different. This typically requires the calling application to perform a transformation of data. Some of the most sophisticated transformation engines are incorporated into ETL (extract, transform, load) tools, such as Informatica or DataMirror. These tools typically transform a large set of data at once instead of transforming individual messages.
本章重点介绍基本消息转换器模式的变体。它没有详细介绍实体之间的结构转换(例如,如果一个模型支持客户和地址之间的多对多关系,但另一个模型包含客户记录上的地址字段,则如何在两个数据模型之间进行转换)。关于呈现数据和关系的主题最古老且仍然最相关的书籍之一是[ Kent ]。
This chapter focuses on variations of the basic Message Translator pattern. It does not go into the details of structural transformations between entities (e.g., how to transform between two data models if one model supports many-to-many relationships between customer and address but the other model includes address fields on the customer record). One of the oldest and still most relevant books on the topic of presenting data and relationships is [Kent].
大多数消息传递系统将消息数据分为标题和正文(请参阅Message )。标头包含消息传递基础结构用于管理消息流的字段。然而,参与集成解决方案的大多数端点系统通常不知道这些额外的数据元素。在某些情况下,系统甚至可能认为这些字段是错误的,因为它们与应用程序使用的消息格式不匹配。另一方面,在应用程序之间路由消息的消息传递组件可能需要标头字段,并且如果消息不包含正确的标头字段,则会认为消息无效。
Most messaging systems divide the message data into a header and a body (see Message). The header contains fields that are used by the messaging infrastructure to manage the flow of messages. However, most endpoint systems that participate in the integration solution generally are not aware of these extra data elements. In some cases, systems may even consider these fields as erroneous because they do not match the message format used by the application. On the other hand, the messaging components that route the messages between the applications may require the header fields and would consider a message invalid if it did not contain the proper header fields.
|
现有系统如何参与对消息格式提出特定要求(例如消息头字段或加密)的消息交换? How can existing systems participate in a messaging exchange that places specific requirements, such as message header fields or encryption, on the message format? |
例如,假设消息传递系统正在使用专有的安全方案。有效的消息必须包含安全凭证,以便其他消息传递组件接受该消息进行处理。这种方案对于防止未经授权的用户将消息输入系统很有用。此外,消息内容可以被加密,以防止未经授权的监听者窃听——这是发布-订阅机制的一个特别重要的问题。然而,通过消息传递系统集成的现有应用程序很可能不知道用户身份或消息加密的概念。因此,“原始”消息需要转换为符合消息传递系统规则的消息。
For example, assume the messaging system is using a proprietary security scheme. A valid message would have to contain security credentials for the message to be accepted for processing by other messaging components. Such a scheme is useful to prevent unauthorized users from feeding messages into the system. Additionally, the message content may be encrypted to prevent eavesdropping by unauthorized listenersa particularly important issue with publish-subscribe mechanisms. However, existing applications that are being integrated via the messaging systems are most likely not aware of the concepts of user identity or message encryption. As a result, "raw" messages need to be translated into messages that comply with the rules of the messaging system.
一些大型企业使用多个消息传递基础设施。因此,消息可能必须使用消息传递桥在消息传递系统之间路由。每个消息系统可能对消息正文和消息头的格式有不同的要求。这种情况是我们可以通过查看现有的基于 TCP/IP 的网络协议来学习的另一种情况。在许多情况下,与另一个系统的连接仅限于特定协议,例如 Telnet 或 Secure Shell ( ssh)。为了使用其他协议(例如 FTP)进行通信,必须将该协议格式封装到符合支持协议的数据包中。在另一端,可以提取数据包有效负载。这个过程称为隧道效应。
Some large enterprises use more than one messaging infrastructure. Thus, a message may have to be routed across messaging systems using a Messaging Bridge. Each messaging system is likely to have different requirements for the format of the message body as well as the header. This scenario is another case where we can learn by looking at existing TCP/IP-based network protocols. In many cases, connectivity to another system is restricted to a specific protocol, such as Telnet or Secure Shell (ssh). In order to enable communication using another protocol (for example, FTP), that protocol format has to be encapsulated into packets that conform to the supported protocol. At the other end, the packet payload can be extracted. This process is called tunneling.
当一种消息格式封装在另一种消息格式中时,系统可能无法访问数据有效负载内的信息。大多数消息传递系统允许组件(例如,消息路由器)仅访问属于已定义消息头的一部分的数据字段。如果一条消息被打包到另一条消息内的数据字段中,则组件可能无法使用原始消息中的字段来执行路由或转换功能。因此,一些数据字段可能必须从原始消息提升到新消息格式的消息头中。
When one message format is encapsulated inside another, the system may lose access to the information inside the data payload. Most messaging systems allow components (for example, a Message Router) to access only data fields that are part of the defined message header. If one message is packaged into a data field inside another message, the component may not be able to use the fields from the original message to perform routing or transformation functions. Therefore, some data fields may have to be elevated from the original message into the message header of the new message format.
|
使用信封包装器将应用程序数据包装在符合消息传递基础结构的信封内。当消息到达目的地时将其拆开。 Use an Envelope Wrapper to wrap application data inside an envelope that is compliant with the messaging infrastructure. Unwrap the message when it arrives at the destination. |
包装和解开消息的过程包括五个步骤:
The process of wrapping and unwrapping a message consists of five steps:
消息源以原始格式发布消息。这种格式通常由应用程序的性质决定,并且不符合消息传递基础设施的要求。
The message source publishes a message in a raw format. This format is typically determined by the nature of the application and does not comply with the requirements of the messaging infrastructure.
包装器获取原始消息并将其转换为符合消息传递系统的消息格式。这可能包括添加消息标头字段、加密消息、添加安全凭证等。
The wrapper takes the raw message and transforms it into a message format that complies with the messaging system. This may include adding message header fields, encrypting the message, adding security credentials, and so on.
消息传递系统传输兼容的消息。
The messaging system transports the compliant messages.
结果消息被传送到解包器。解包器会反转包装器所做的任何修改。这可能包括删除标头字段、解密消息或验证安全凭证。
A resulting message is delivered to the unwrapper. The unwrapper reverses any modifications the wrapper made. This may include removing header fields, decrypting the message, or verifying security credentials.
消息接收者收到“明文”消息。
The message recipient receives a "clear text" message.
信封通常包装消息头和消息正文或有效负载。我们可以将标头视为邮政信封外部的信息:消息传递系统使用它来路由和跟踪消息。信封的内容是有效负载或正文,消息传递基础设施在到达目的地之前并不关心它(在某些限制内)。
An envelope typically wraps both the message header and the message body, or payload. We can think of the header as being the information on the outside of a postal envelope: It is used by the messaging system to route and track the message. The contents of the envelope is the payload or bodythe messaging infrastructure does not care much about it (within certain limitations) until it arrives at the destination.
包装器通常会向原始消息添加信息。例如,在通过邮政系统发送内部消息之前,必须查找邮政编码。从这个意义上说,包装器包含了内容丰富器的某些方面。然而,包装器并没有丰富实际的信息内容,而是添加了消息路由、跟踪和处理所需的信息。该信息可以动态创建(例如,创建唯一的消息 ID 或添加时间戳),可以从基础设施中提取(例如,检索安全上下文),或者数据可以包含在原始消息中主体,然后由包装器分割成消息头(例如,原始消息中包含的关键字段)。最后一个选项有时称为升级,因为特定字段从隐藏在正文中“升级”为在标头中显着可见。
It is typical for wrappers to add information to the raw message. For example, before an internal message can be sent through the postal system, a ZIP code has to be looked up. In that sense, wrappers incorporate some aspects of a Content Enricher. However, wrappers do not enrich the actual information content, but add information that is necessary for the routing, tracking, and handling of messages. This information can be created on the fly (e.g., creating a unique message ID or adding a time stamp), it can be extracted from the infrastructure (e.g., retrieval of a security context), or the data may be contained in the original message body and then split by the wrapper into the message header (e.g., a key field contained in the raw message). This last option is sometimes referred to as promotion because a specific field is "promoted" from being hidden inside the body to being prominently visible in the header.
通常,多个包装器和拆包器被链接起来(请参阅以下邮政系统示例),利用分层协议模型。这会导致消息的有效负载包含一个新信封,而新信封又包装了标头和有效负载部分(见图)。
Frequently, multiple wrappers and unwrappers are chained (see the following postal system example), taking advantage of the layered protocol model. This results in a situation where the payload of a message contains a new envelope, which in turn wraps a header and a payload section (see figure).
包装器链创建了分层信封结构
A Chain of Wrappers Creates a Hierarchical Envelope Structure
|
示例: SOAP 消息格式 Example: SOAP Message Format 基本 SOAP 消息格式 [ SOAP 1.1] 比较简单。它指定包含消息头和消息正文的信封。以下示例说明正文如何包含另一个信封,而另一个信封又包含另一个标头和正文。组合消息被发送到中介,该中介解开外部消息并转发内部消息。在跨越信任边界时,这种中介链是非常常见的。我们可以对所有消息进行编码并将它们包装在另一条消息中,以便没有中间人可以看到消息内容或标头(例如,消息的地址可能是机密的)。然后,接收者解开消息,解码有效负载,并将未编码的消息通过可信环境传递。 The basic SOAP message format [SOAP 1.1] is relatively simple. It specifies an envelope that contains a message header and a message body. The following example illustrates how the body can contain another envelope, which in turn contains another header and body. The combined message is sent to an intermediary that unwraps the outside message and forwards the inside message. This chaining of intermediaries is very common when crossing trust boundaries. We may encode all our messages and wrap them inside another message so that no intermediary can see the message content or header (e.g., the address of a message may be confidential). The recipient then unwraps the message, decodes the payload, and passes the unencoded message through the trusted environment. <env:信封 xmlns:env="http://www.w3.org/2001/06/soap-envelope"> <env:Header env:actor="http://example.org/xmlsec/Bob"> <n:forward xmlns:n="http://example.org/xmlsec/forwarding"> <n:窗口>120</n:窗口> </n:转发> </env:标题> <环境:正文> <env:信封 xmlns:env="http://www.w3.org/2001/06 <env:Envelope xmlns:env="http://www.w3.org/2001/06/soap-envelope"> <env:Header env:actor="http://example.org/xmlsec/Bob"> <n:forward xmlns:n="http://example.org/xmlsec/forwarding"> <n:window>120</n:window> </n:forward> </env:Header> <env:Body> <env:Envelope xmlns:env="http://www.w3.org/2001/06 /soap-envelope"> <env:Header env:actor="http://example.org/xmlsec/Alice"/> <env:Body> <secret xmlns="http://example.org/xmlsec/message"> The black squirrel rises at dawn</secret> </env:Body> </env:Envelope> </env:Body> </env:Envelope> |
|
示例: TCP/IP Example: TCP/IP 虽然我们通常使用TCP/IP作为一个术语,但它实际上包含两个协议。IP 协议提供基本的寻址和路由服务,而 TCP 则提供位于 IP 之上的可靠的、面向连接的协议。遵循 OSI 层模型,TCP 是传输协议,而 IP 是网络协议。通常,TCP/IP 数据通过以太网传输,以太网实现了链路层。 While we commonly use TCP/IP as one term, it actually comprises two protocols. The IP protocol provides basic addressing and routing services, while TCP provides a reliable, connection-oriented protocol that is layered on top of IP. Following the OSI layer model, TCP is a transport protocol, while IP is a network protocol. Typically, TCP/IP data is transported over an Ethernet network, which implements the link layer. 因此,应用程序数据首先被包装到 TCP 信封中,然后被包装到 IP 信封中,最后被包装到以太网信封中。由于网络是面向流的,因此信封可以由标头和尾部组成,标记数据流的开始和结束。下页的图说明了通过以太网传输的应用程序数据的结构。 As a result, application data is wrapped into a TCP envelope first, which is then wrapped into an IP envelope, which is then wrapped into an Ethernet envelope. Since networks are stream-oriented, an envelope can consist of both a header and a trailer, marking the beginning and the end of the data stream. The figure on the following page illustrates the structure of application data traveling over the Ethernet. 应用程序数据被包装在多个信封内以通过网络传输 Application Data Is Wrapped Inside Multiple Envelopes to be Transported over the Network 您可以看到应用程序数据连续包装到多个信封中:TCP(传输信封)、IP(网络信封)和以太网(链路信封)。TCP 和 IP 信封仅包含报头,而以太网信封则添加报头和报尾。如果您对 TCP/IP 的更多细节感兴趣,[ Stevens ] 一定能满足您的求知欲。 You can see the successive wrapping of the application data into multiple envelopes: TCP (transport envelope), IP (network envelope), and Ethernet (link envelope). The TCP and the IP envelopes consist of only a header, while the Ethernet envelope adds both a header and a trailer. If you are interested in more details about TCP/IP, [Stevens] is guaranteed to quench your thirst for knowledge. |
|
示例: 邮政系统 Example: The Postal System 信封包装纸模式可以与邮政系统进行比较(参见下页的图)。假设一名员工为同事创建了一份内部备忘录。任何纸张都可以是此消息有效负载的可接受格式。为了交付备忘录,必须将其“包装”到公司内部的信封中,其中包含收件人的姓名和部门代码。如果收件人在单独的机构工作,该公司内部消息将被装入一个大信封并通过美国邮政服务邮寄。为了使新邮件符合 USPS 要求,需要使用带有邮政编码和邮资的新信封。美国邮政局可能决定通过航空运输该信封。为此,它将特定区域的所有信封放入邮袋中,其地址是一个条形码,其中包含目的地机场的三个字母的机场代码。一旦邮袋到达目的地机场,包装顺序就会相反,直到同事收到原始备忘录。这个例子说明了这个术语隧道:邮政邮件可以通过空运进行“隧道”传输,就像 UDP 多播数据包可以通过 TCP/IP 连接进行隧道传输一样,以便到达不同的 WAN 网段。 The Envelope Wrapper pattern can be compared to the postal system (see figure on the next page). Let's assume an employee creates an internal memo to a fellow employee. Any sheet of paper will be an acceptable format for this message payload. In order for the memo to be delivered, it has to be "wrapped" into an intra-company envelope that contains the recipient's name and department code. If the recipient works in a separate facility, this intra-company message will be stuffed into a large envelope and mailed via the U.S. Postal Service. In order to make the new message comply with the USPS requirements, it needs to feature a new envelope with ZIP code and postage. The USPS may decide to transport this envelope via air. To do so, it stuffs all envelopes for a specific region into a mailbag, which is addressed with a bar code featuring the three-letter airport code for the destination airport. Once the mailbag arrives at the destination airport, the wrapping sequence is reversed until the original memo is received by the coworker. This example illustrates the term tunneling: Postal mail may be "tunneled" through air freight just as UDP mulitcast packets may be tunneled over a TCP/IP connection in order to reach a different WAN segment. 邮政系统示例说明了使用管道和过滤器架构链接包装器和拆包器的常见做法。消息可能由多个步骤进行包装,并且需要通过一系列对称的解包步骤进行解包。正如管道和过滤器中所述,保持各个步骤彼此独立使消息传递基础结构能够灵活地添加或删除包装和展开步骤。例如,可能不再需要加密,因为所有流量都通过 VPN 而不是公共互联网进行路由。 The postal system example illustrates the common practice of chaining wrappers and unwrappers using the Pipes and Filters architecture. Messages may be wrapped by more than one step and need to be unwrapped by a symmetric sequence of unwrapping steps. As laid out in Pipes and Filters, keeping the individual steps independent from each other gives the messaging infrastructure the flexibility to add or remove wrapping and unwrapping steps. For example, encryption may no longer be required because all traffic is routed across a VPN as opposed to the public Internet. |
当从一个系统向另一个系统发送消息时,目标系统通常需要比源系统可以提供的信息更多的信息。例如,传入的地址消息可能只包含邮政编码,因为设计者认为存储冗余的城市和州信息是多余的。另一个系统可能想要指定城市和州以及邮政编码字段。然而另一个系统实际上可能不使用州缩写,而是拼出州名称,因为它使用自由格式地址来支持国际地址。同样,一个系统可能会向我们提供客户 ID,但接收系统实际上需要客户姓名和地址。在另一种情况下,订单管理系统发送的订单消息可能只包含订单号,但我们需要找到与该订单关联的客户 ID,以便将其传递给客户管理系统。场景很丰富。
When sending messages from one system to another, it is common for the target system to require more information than the source system can provide. For example, incoming Address messages may just contain the ZIP code because the designers felt that storing a redundant city and state information would be superfluous. Likely, another system will want to specify city and state as well as a ZIP code field. Yet another system may not actually use state abbreviations, but spell the state name out because it uses freeform addresses in order to support international addresses. Likewise, one system may provide us with a customer ID, but the receiving system actually requires the customer name and address. In yet another situation, an Order message sent by the order management system may just contain an order number, but we need to find the customer ID associated with that order so we can pass it to the customer management system. The scenarios are plentiful.
|
如果消息发起者没有提供所有必需的数据项,我们如何与另一个系统通信? How do we communicate with another system if the message originator does not have all the required data items available? |
此问题是消息转换器的特例,因此一些相同的注意事项也适用。然而,这个问题与消息翻译器中描述的基本示例略有不同。 消息转换器的描述假设接收应用程序所需的数据已包含在传入消息中,尽管格式错误。在这个新案例中,这不是一个简单的重新排列字段的问题;而是一个简单的问题。我们实际上需要向消息中注入附加信息。
This problem is a special case of the Message Translator, so some of the same considerations apply. However, this problem is slightly different from the basic examples described in the Message Translator. The description of the Message Translator assumed that the data needed by the receiving application is already contained in the incoming message, albeit in the wrong format. In this new case, it is not a simple matter of rearranging fields; we actually need to inject additional information to the message.
会计系统需要的信息多于调度系统所能提供的信息
The Accounting System Requires More Information Than the Scheduling System Can Deliver
让我们考虑以下示例(见图)。医院调度系统发布一条消息,宣布患者已完成就诊。该消息包含患者的名字、患者 ID 以及就诊日期。为了让会计系统记录这次就诊并通知保险公司,它需要患者的完整姓名、保险公司和患者的社会安全号码。然而,调度系统并不存储这些信息;它包含在客户服务系统中。我们有什么选择?
Let's consider the following example (see figure). A hospital scheduling system publishes a message announcing that the patient has completed a doctor's visit. The message contains the patient's first name, his or her patient ID, and the date of the visit. In order for the accounting system to log this visit and inform the insurance company, it requires the full patient name, the insurance carrier, and the patient's social security number. However, the scheduling system does not store this information; it is contained in the customer care system. What are our options?
浓缩器问题的可能解决方案
Possible Solutions for the Enricher Problem
选项A: 我们可以修改调度系统,以便它可以存储附加信息。当客户服务系统中的客户信息发生变化时(例如,由于患者更换保险公司),需要将这些变化复制到调度系统。调度系统现在可以发送包含所有必需信息的消息。不幸的是,这种方法有两个明显的缺点。首先,需要对调度系统的内部结构进行修改。在大多数情况下,调度系统是打包的应用程序,并且可能不允许这种类型的修改。其次,即使调度系统是可定制的,我们也需要考虑到我们正在根据另一个系统的特定需求对系统进行更改。例如,如果我们还想向患者发送一封确认就诊的信件,我们将不得不再次更改日程安排系统以适应客户的邮寄地址。如果我们将调度系统与使用 Doctor Visit 消息的应用程序的具体细节分离,那么集成解决方案的可维护性将会更高。
选项 B: 调度系统可以在发送医生就诊消息之前向客户服务系统请求 SSN 和运营商数据,而不是将客户信息存储在调度系统内。这就解决了第一个问题,我们不再需要修改调度系统的存储。然而,第二个问题仍然存在:调度系统需要知道需要SSN和运营商信息,以便通知计费系统。因此,该消息的语义更类似于“Notify Insurance”而不是“Doctor Visit”。在松散耦合的系统中,我们不希望一个系统指示下一个系统做什么。我们改为发送事件消息并让其他系统决定要做什么。此外,该解决方案将调度系统与客户服务系统更紧密地结合在一起,因为调度系统现在需要知道从哪里获取丢失的数据。这将调度系统与会计系统和客户服务系统联系起来。这种类型的耦合是不受欢迎的,因为它会导致脆弱的集成解决方案。
选项C: 如果我们首先将消息发送到客户服务系统而不是会计系统,我们可以避免其中一些依赖性。然后,客户服务系统可以获取所有必需的信息,并将包含所有必需数据的消息发送到会计系统。这很好地将调度系统与后续消息流解耦。但现在我们在客服系统内部实现了病人看病后保险公司收到账单的业务规则。这就需要我们修改客户服务系统内部的逻辑。如果客户服务系统是打包的应用程序,则这种修改可能很困难或不可能。即使我们可以做出这样的修改,我们现在让客户服务系统间接负责发送计费消息。如果会计系统所需的所有数据项在客户服务系统中都可用,这可能不是问题。但是,如果必须从其他系统检索某些字段,我们就会遇到与开始时类似的情况。
选项 D(未显示): 我们还可以修改会计系统,只需要客户 ID,并从客户服务系统检索 SSN 和运营商信息。这种方法有两个缺点。首先,我们现在将会计系统与客户服务系统结合起来。其次,这个选项再次假设我们拥有对会计系统的控制权。在大多数情况下,会计系统是一个打包的应用程序,定制选项有限。
Option A: We could modify the scheduling system so it can store the additional information. When the customer's information changes in the customer care system (e.g., because the patient switches insurance carriers), the changes need to be replicated to the scheduling system. The scheduling system can now send a message that includes all required information. Unfortunately, this approach has two significant drawbacks. First, it requires a modification to the scheduling system's internal structure. In most cases, the scheduling system is a packaged application and may not allow this type of modification. Second, even if the scheduling system is customizable, we need to consider that we are making a change to the system based on the specific needs of another system. For example, if we also want to send a letter to the patient confirming the visit, we would have to change the scheduling system again to accommodate the customer's mailing address. The integration solution would be much more maintainable if we decoupled the scheduling system from the specifics of the applications that consume the Doctor Visit message.
Option B: Instead of storing the customer's information inside the scheduling system, the scheduling system could request the SSN and carrier data from the customer care system just before it sends the Doctor Visit message. This solves the first problemwe no longer have to modify the storage of the scheduling system. However, the second problem remains: The scheduling system needs to know that the SSN and carrier information is required in order to notify the accounting system. Therefore, the semantics of the message are more similar to Notify Insurance than to Doctor Visit. In a loosely coupled system, we do not want one system to instruct the next one on what to do. We instead send an Event Message and let the other systems decide what to do. In addition, this solution couples the scheduling system more tightly to the customer care system because the scheduling system now needs to know where to get the missing data. This ties the scheduling system to both the accounting system and the customer care system. This type of coupling is undesirable because it leads to brittle integration solutions.
Option C: We can avoid some of these dependencies if we send the message to the customer care system first instead of to the accounting system. The customer care system can then fetch all the required information and send a message with all required data to the accounting system. This decouples the scheduling system nicely from the subsequent flow of the message. However, now we implement the business rule that the insurance company receives a bill after the patient visits the doctor inside the customer care system. This requires us to modify the logic inside the customer care system. If the customer care system is a packaged application, this modification may be difficult or impossible. Even if we can make this modification, we now make the customer care system indirectly responsible for sending billing messages. This may not be a problem if all the data items required by the accounting system are available inside the customer care system. However, if some of the fields have to be retrieved from other systems, we are in a situation similar to where we started.
Option D (not shown): We could also modify the accounting system to require only the customer ID and to retrieve the SSN and carrier information from the customer care system. This approach has two disadvantages. First, we now couple the accounting system to the customer care system. Second, this option again assumes that we have control over the accounting system. In most cases, the accounting system is a packaged application with limited options for customization.
|
使用专门的转换器(内容丰富器)来访问外部数据源,以便用缺失的信息来扩充消息。 Use a specialized transformer, a Content Enricher, to access an external data source in order to augment a message with missing information. |
内容丰富器使用传入消息内的信息(例如关键字段)从外部源检索数据。内容丰富器从资源中检索所需的数据后,会将数据附加到消息中。传入消息中的原始信息可能会被保留到结果消息中,也可能不再需要,具体取决于接收应用程序的特定需求。
The Content Enricher uses information inside the incoming message (e.g., key fields) to retrieve data from an external source. After the Content Enricher retrieves the required data from the resource, it appends the data to the message. The original information from the incoming message may be carried over into the resulting message or may no longer be needed, depending on the specific needs of the receiving application.
内容丰富器注入的附加信息必须在系统中的某个位置可用。以下是新数据最常见的来源:
The additional information injected by the Content Enricher has to be available somewhere in the system. Following are the most common sources for the new data:
计算: 内容丰富器也许能够计算缺失的信息。在这种情况下,算法包含附加信息。例如,如果接收系统需要城市和州缩写,但传入消息仅包含邮政编码,则算法可以提供城市和州信息。或者,接收系统可能需要指定消息总大小的数据格式。内容丰富器可以添加所有消息字段的长度,从而计算消息大小。这种形式的内容丰富器与基本的消息因为它不需要外部数据源。
Computation: The Content Enricher may be able to compute the missing information. In this case, the algorithm incorporates the additional information. For example, if the receiving system requires a city and state abbreviation, but the incoming message contains only a ZIP code, the algorithm can supply the city and state information. Or, a receiving system may require a data format that specifies the total size of the message. The Content Enricher can add the length of all message fields and thus compute the message size. This form of Content Enricher is very similar to the basic Message Translator because it needs no external data source.
环境: 内容丰富器可能能够从操作环境检索附加数据。最常见的例子是时间戳。例如,接收系统可能要求每个消息携带时间戳。如果发送系统不包含该字段,则内容丰富器可以从操作系统获取当前时间并将其添加到消息中。
Environment: The Content Enricher may be able to retrieve the additional data from the operating environment. The most common example is a time stamp. For example, the receiving system may require each message to carry a time stamp. If the sending system does not include this field, the Content Enricher can get the current time from the operating system and add it to the message.
另一种系统: 此选项是最常见的。内容丰富器必须从另一个系统检索丢失的数据。该数据资源可以采用多种形式,包括数据库、文件、LDAP 目录、应用程序或手动输入缺失数据的用户。
Another System: This option is the most common one. The Content Enricher has to retrieve the missing data from another system. This data resource can take on a number of forms, including a database, a file, an LDAP directory, an application, or a user who manually enters missing data.
在许多情况下,内容丰富器所需的外部资源可能位于另一个系统上,甚至位于企业外部。因此,内容丰富器和资源之间的通信可以通过消息传递或通过任何其他通信机制进行(参见第2章“集成样式”)。由于Content Enricher和数据源之间的交互根据定义是同步的( Content Enricher在数据源返回所请求的数据之前无法发送丰富的消息),同步协议(例如,HTTP 或到数据库的 ODBC 连接)可能会比使用异步消息传递带来更好的性能。内容丰富器和数据源本质上是紧密耦合的,因此通过消息通道实现松散耦合并不那么重要。
In many cases, the external resource required by the Content Enricher may be situated on another system or even outside the enterprise. Accordingly, the communication between the Content Enricher and the resource can occur via Messaging or via any other communication mechanism (see Chapter 2, "Integration Styles"). Since the interaction between the Content Enricher and the data source is by definition synchronous (the Content Enricher cannot send the enriched message until the data source returns the requested data), a synchronous protocol (e.g., HTTP or an ODBC connection to a database) may result in better performance than using asynchronous messaging. The Content Enricher and the data source are inherently tightly coupled, so achieving loose coupling through Message Channels is not as important.
回到我们的示例,我们可以插入内容丰富器以从客户服务系统检索附加数据(见图)。这样,调度系统就可以很好地摆脱处理保险信息或客户服务系统的负担。它所要做的就是发布医生就诊消息。内容丰富器组件负责检索所需的数据。会计系统也独立于客户服务系统。
Returning to our example, we can insert a Content Enricher to retrieve the additional data from the customer care system (see figure). This way, the scheduling system is nicely decoupled from having to deal with insurance information or the customer care system. All it has to do is publish the Doctor Visit message. The Content Enricher component takes care of retrieving the required data. The accounting system also remains independent from the customer care system.
将 Enricher 应用于患者示例
Applying the Enricher to the Patient Example
内容丰富器在许多场合用于解析消息中包含的引用。为了使消息保持较小且易于管理,我们通常选择传递对对象的简单引用,而不是传递包含所有数据元素的完整对象。这些引用通常采用密钥或唯一 ID 的形式。当系统需要处理消息时,我们需要根据原始消息中包含的对象引用来检索所需的数据项。我们使用内容丰富器来执行此任务。其中涉及一些明显的权衡。使用引用可以减少原始消息中的数据量,但需要在资源中进行额外的查找。使用引用是否可以提高性能取决于有多少组件可以简单地对引用进行操作,以及有多少组件需要使用内容丰富器来恢复一些原始消息内容。例如,如果消息在到达最终收件人之前经过一长串中介,则使用对象引用可以显着减少消息流量。我们可以插入内容丰富器作为最终收件人将丢失的信息加载到邮件中之前的最后一步。如果消息已经包含我们可能不想一路携带的数据,我们可以使用声明检查来存储数据并获取对其的引用。
The Content Enricher is used on many occasions to resolve references contained in a message. In order to keep messages small and easy to manage, we often choose to pass simple references to objects rather than pass a complete object with all data elements. These references usually take the form of keys or unique IDs. When the message needs to be processed by a system, we need to retrieve the required data items based on the object references included in the original message. We use a Content Enricher to perform this task. There are some apparent trade-offs involved. Using references reduces the data volume in the original messages, but requires additional lookups in the resource. Whether the use of references improves performance depends on how many components can operate simply on references versus how many components need to use a Content Enricher to restore some of the original message content. For example, if a message passes through a long list of intermediaries before it reaches the final recipient, using an object reference can decrease message traffic significantly. We can insert a Content Enricher as the last step before the final recipient to load the missing information into the message. If the message already contains data that we might not want to carry along the way, we can use the Claim Check to store the data and obtain a reference to it.
|
示例: 与外部各方的沟通 Example: Communication with External Parties 当与要求消息符合特定消息标准(例如,ebXML)的外部各方进行通信时,也通常使用内容丰富器。这些标准中的大多数都需要带有长数据列表的大消息。如果我们保持内部消息尽可能简单,然后每当我们向组织外部发送消息时使用内容丰富器来添加缺少的字段,那么我们通常可以显着简化内部操作。同样,我们可以使用内容过滤器从传入消息中去除不必要的信息(见图)。 A Content Enricher is also commonly used when communicating with external parties that require messages to be compliant with a specific message standard (e.g., ebXML). Most of these standards require large messages with a long list of data. We can usually simplify our internal operations significantly if we keep internal messages as simple as possible and then use a Content Enricher to add the missing fields whenever we send a message outside of the organization. Likewise, we can use a Content Filter to strip unnecessary information from incoming messages (see figure). 与外部方通信时使用内容丰富器/内容过滤器对 Using a Content Enricher/Content Filter Pair When Communicating with External Parties |
当消息接收者需要比消息创建者提供的更多或不同的数据元素时,内容丰富器可以帮助我们。令人惊讶的是,在许多情况下都需要相反的效果:从消息中删除数据元素。
The Content Enricher helps us in situations where a message receiver requires moreor differentdata elements than the message creator provides. There are surprisingly many situations where the opposite effect is desired: removing data elements from a message.
|
当您只对少数数据项感兴趣时,如何简化对大消息的处理? How do you simplify dealing with a large message when you are interested only in a few data items? |
为什么我们要从消息中删除有价值的数据元素?一个常见的原因是安全。从服务请求数据的应用程序可能未被授权查看回复消息包含的所有数据元素。服务提供商可能不了解安全方案并且可能总是返回所有数据元素而不管用户身份。我们需要添加一个步骤,根据请求者经过验证的身份删除敏感数据。例如,工资系统可能仅公开一个返回有关员工的所有数据的简单接口。这些数据可能包括工资信息、社会安全号码和其他敏感信息。如果您正在尝试构建一项返回员工在公司的入职日期的服务,
Why would we want to remove valuable data elements from a message? One common reason is security. An application that requests data from a service may not be authorized to see all data elements that the reply message contains. The service provider may not have knowledge of a security scheme and may always return all data elements regardless of user identity. We need to add a step that removes sensitive data based on the requestor's proven identity. For example, the payroll system may expose only a simple interface that returns all data about an employee. This data may include payroll information, social security numbers, and other sensitive information. If you are trying to build a service that returns an employee's start date with the company, you may want to eliminate all sensitive information from the result message before passing it back to the requestor.
删除数据元素的另一个原因是为了简化消息处理并减少网络流量。在许多情况下,流程是由从业务合作伙伴收到的消息启动的。出于显而易见的原因,希望与第三方的通信基于标准化的消息格式。许多标准机构和委员会为某些行业和应用程序定义了标准 XML 数据格式。著名的例子有 RosettaNet、ebXML、ACORD 等等。虽然这些 XML 格式对于根据商定的标准与外部各方进行交互非常有用,但“委员会设计”方法通常会产生非常大的文档。许多文档都有数百个字段,由多个嵌套级别组成。如此大的文档很难用于内部消息交换。例如,如果要映射的文档有数百个元素,则大多数可视化(拖放式)转换工具将变得无法使用。此外,调试也成为一场重大噩梦。因此,我们希望简化传入文档,使其仅包含内部处理步骤实际需要的元素。从某种意义上说,删除元素丰富了此类消息的有用性,因为删除了冗余和不相关的字段,留下了更有意义的消息,并减少了开发人员犯错的空间。调试成为一场重大噩梦。因此,我们希望简化传入的文档,以仅包含我们内部处理步骤实际需要的元素。从某种意义上说,删除元素丰富了此类消息的有用性,因为删除了冗余和不相关的字段,留下了更有意义的消息,并减少了开发人员犯错的空间。调试成为一场重大噩梦。因此,我们希望简化传入的文档,以仅包含我们内部处理步骤实际需要的元素。从某种意义上说,删除元素丰富了此类消息的有用性,因为删除了冗余和不相关的字段,留下了更有意义的消息,并减少了开发人员犯错的空间。
Another reason to remove data elements is to simplify message handling and to reduce network traffic. In many instances, processes are initiated by messages received from business partners. For obvious reasons, it is desirable to base communication with third parties on a standardized message format. A number of standards bodies and committees define standard XML data formats for certain industries and applications. Well-known examples are RosettaNet, ebXML, ACORD, and many more. While these XML formats are useful to conduct interaction with external parties based on an agreed-upon standard, the "design-by-committee" approach usually results in very large documents. Many of the documents have hundreds of fields, consisting of multiple nested levels. Such large documents are difficult to work with for internal message exchange. For example, most visual (drag-drop style) transformation tools become unusable if the documents to be mapped have hundreds of elements. Also, debugging becomes a major nightmare. Therefore, we want to simplify the incoming documents to include only the elements we actually require for our internal processing steps. In a sense, removing elements enriches the usefulness of such a message, because redundant and irrelevant fields are removed, leaving a more meaningful message and less room for developer mistakes.
|
使用内容过滤器从邮件中删除不重要的数据项目,仅保留重要的项目。 Use a Content Filter to remove unimportant data items from a message, leaving only important items. |
内容过滤器不一定只是删除数据元素。内容过滤器对于简化消息的结构也很有用。通常,消息被表示为树结构。许多源自外部系统或打包应用程序的消息都包含许多级别的嵌套重复组,因为它们是根据通用的标准化数据库结构建模的。通常,已知的约束和假设使得这种级别的嵌套变得多余,并且内容过滤器可用于将层次结构“扁平化”为简单的元素列表,这些元素可以更容易地被其他系统理解和处理。
The Content Filter does not necessarily just remove data elements. A Content Filter is also useful to simplify the structure of the message. Often, messages are represented as tree structures. Many messages originating from external systems or packaged applications contain many levels of nested, repeating groups because they are modeled after generic, normalized database structures. Frequently, known constraints and assumptions make this level of nesting superfluous, and a Content Filter can be used to "flatten" the hierarchy into a simple list of elements that can be more easily understood and processed by other systems.
使用内容过滤器展平消息层次结构
Flattening a Message Hierarchy with a Content Filter
多个内容过滤器可以用作静态拆分器(请参阅拆分器) ,将一条复杂的消息分解为单独的消息,每个消息处理大消息的某个方面(请参阅下一页顶部的图)。
Multiple Content Filters can be used as a static Splitter (see Splitter) to break one complex message into individual messages that each deal with a certain aspect of the large message (see figure at the top of the next page).
使用多个内容过滤器作为静态拆分器
Using Multiple Content Filters as a Static Splitter
|
示例: 数据库适配器 Example: Database Adapter 许多集成套件提供通道适配器来连接到现有系统。在许多情况下,这些适配器发布的消息的格式类似于应用程序的内部结构。例如,假设我们将数据库适配器连接到具有以下架构的数据库: Many integration suites provide Channel Adapters to connect to existing systems. In many cases, these adapters publish messages whose format resembles the internal structure of the application. For example, let's assume we connect a database adapter to a database with the following schema: 数据库架构示例 An Example Database Schema 物理数据库模式最好将相关实体存储在通过外键和关系表链接的单独表中(例如,ACCOUNT_CONTACT链接ACCOUNT和CONTACT表) 。许多商业数据库适配器将这些相关表转换为分层消息结构,该结构可以包含可能与消息接收者不相关的附加字段,例如主键和外键。为了使处理消息更容易,我们可以使用内容过滤器来扁平化消息结构并仅提取相关字段。该示例显示了内容过滤器的实现使用视觉转换工具。我们可以看到如何将消息从分布在多个级别的十多个字段减少为具有五个字段的简单消息。其他组件使用简化的消息会更容易(也更有效)。 It is desirable for a physical database schema to store related entities in separate tables that are linked by foreign keys and relation tables (e.g., ACCOUNT_CONTACT links the ACCOUNT and CONTACT tables). Many commercial database adapters translate these related tables into a hierarchical message structure that can contain additional fields such as primary and foreign keys that may not be relevant to the message receiver. In order to make processing a message easier, we can use a Content Filter to flatten the message structure and extract only relevant fields. The example shows the implementation of a Content Filter using a visual transformation tool. We can see how we reduce the message from over a dozen fields spread across multiple levels into a simple message with five fields. It will be much easier (and more efficient) for other components to work with the simplified message. 简化数据库适配器发布的消息 Simplifying a Message Published by a Database Adapter 内容过滤器并不是解决此特定问题的唯一方法。例如,我们可以在数据库中配置一个视图来解析表关系并返回一个简单的结果集。如果我们能够向数据库添加视图,这可能是一个简单的选择。在许多情况下,企业集成的目标是尽可能减少侵入性,并且该准则可能包括不向数据库添加视图。 A Content Filter is not the only solution to this particular problem. For example, we could configure a view in the database that resolves the table relationships and returns a simple result set. This may be a simple choice if we have the ability to add views to the database. In many situations, enterprise integration aims to be as little intrusive as possible and that guideline may include not adding views to a database. |
内容丰富器告诉我们如何处理消息缺少所需数据项的情况。为了达到相反的效果,内容过滤器允许我们从消息中删除不感兴趣的数据项。但是,我们可能只想暂时删除字段。例如,消息可能包含消息流稍后需要的一组数据项,但并非所有中间处理步骤都需要这些数据项。我们可能不想在每个处理步骤中携带所有这些信息,因为这可能会导致性能下降,并且由于我们携带了太多额外的数据,因此使调试变得更加困难。
The Content Enricher tells us how we can deal with situations where our message is missing required data items. To achieve the opposite effect, the Content Filter lets us remove uninteresting data items from a message. However, we may want to remove fields only temporarily. For example, a message may contain a set of data items that are needed later in the message flow but that are not necessary for all intermediate processing steps. We may not want to carry all this information through each processing step, because it can cause performance degradation, and it makes debugging harder because we carry so much extra data.
|
如何在不牺牲信息内容的情况下减少整个系统发送的消息的数据量? How can we reduce the data volume of a message sent across the system without sacrificing information content? |
通过消息移动大量数据可能效率低下。一些消息传递系统甚至对消息的大小有硬性限制。其他消息传递系统使用数据的 XML 表示形式,这可能会将消息的大小增加一个数量级或更多。因此,虽然消息传递提供了最可靠、响应最快的信息传输方式,但它可能不是最有效的。
Moving large amounts of data via messages may be inefficient. Some messaging systems even have hard limits as to the size of messages. Other messaging systems use an XML representation of data, which can increase the size of a message by an order of magnitude or more. So, while messaging provides the most reliable and responsive way to transmit information, it may not be the most efficient.
此外,在消息中携带较少的数据可以防止中间系统依赖于不适合它们的信息。例如,如果我们通过一系列中间体将地址信息从一个系统发送到另一个系统,则中间体可能会开始对地址数据做出假设,从而变得依赖于消息数据格式。使消息尽可能小可以减少引入此类隐藏假设的可能性。
Also, carrying less data in a message keeps intermediate systems from depending on information that was not intended for them. For example, if we send address information from one system to another via a series of intermediates, the intermediates could start making assumptions about the address data and therefore become dependent on the message data format. Making messages as small as possible reduces the possibility of introducing such hidden assumptions.
简单的内容过滤器可以帮助我们减少数据量,但不能保证我们以后可以恢复消息内容。因此,我们需要以一种稍后可以检索的方式存储完整的消息信息。
A simple Content Filter helps us reduce data volume but does not guarantee that we can restore the message content later on. Therefore, we need to store the complete message information in a way that we can retrieve it later.
因为我们需要存储每条消息的数据,所以我们需要一个密钥来检索与消息关联的正确数据项。我们可以使用消息 ID 作为密钥,但这不允许后续组件传递密钥,因为消息 ID 会随着每条消息而变化。
Because we need to store data for each message, we need a key to retrieve the correct data items associated with a message. We could use the message ID as the key, but that would not allow subsequent components to pass the key on, because the message ID changes with each message.
|
将消息数据存储在持久存储中并将声明检查传递给后续组件。这些组件可以使用声明检查来检索存储的信息。 Store message data in a persistent store and pass a Claim Check to subsequent components. These components can use the Claim Check to retrieve the stored information. |
声明检查模式包含以下步骤:
The Claim Check pattern consists of the following steps:
带有数据的消息到达。
A message with data arrives.
检查行李组件生成该信息的唯一密钥。该密钥稍后将用作索赔检查。
The Check Luggage component generates a unique key for the information. This key will be used later as the Claim Check.
检查行李组件从消息中提取数据并将其存储在持久存储中,例如文件或数据库。它将存储的数据与密钥相关联。
The Check Luggage component extracts the data from the message and stores it in a persistent store, such as a file or a database. It associates the stored data with the key.
它从消息中删除持久数据并添加声明检查。
It removes the persisted data from the message and adds the Claim Check.
另一个组件可以使用内容丰富器来检索基于声明。
Another component can use a Content Enricher to retrieve the data based on the Claim Check.
此过程类似于机场的行李检查。如果您不想随身携带所有行李,只需在航空公司柜台托运即可。作为回报,您会在机票上收到一张贴纸,上面有一个参考号,可以唯一标识您托运的每件行李。到达最终目的地后,您可以取回行李。
This process is analogous to a luggage check at the airport. If you do not want to carry all of your luggage with you, you simply check it at the airline counter. In return, you receive a sticker on your ticket that has a reference number that uniquely identifies each piece of luggage you checked. Once you reach your final destination, you can retrieve your luggage.
如图所示,原始消息中包含的数据仍然需要“移动”到最终目的地。那么,我们有什么收获吗?是的,因为通过消息传递传输数据可能比将数据存储在中央数据存储中效率低。例如,消息可以经历不需要大量数据的多个路由步骤。使用消息传递系统,消息数据将在每一步被编组和解编,可能被加密和解密。这种类型的操作可能非常消耗 CPU 资源,并且对于任何中间步骤都不需要而仅最终目的地需要的数据来说是完全没有必要的。索赔检查在消息经过多个组件并返回到发送者的情况下也能很好地工作。在这种情况下,检查行李组件和内容丰富器位于同一组件的本地,并且数据永远不必通过网络传输(见图):
As the figure illustrates, the data that was contained in the original message still needs to be "moved" to the ultimate destination. So, did we gain anything? Yes, because transporting data via messaging may be less efficient than storing it in a central datastore. For example, the message may undergo multiple routing steps that do not require the large amount of data. Using a messaging system, the message data would be marshaled and unmarshaled, possibly encrypted and decrypted, at every step. This type of operation can be very CPU-intensive and would be completely unnecessary for data that is not needed by any intermediate step but only by the final destination. The Claim Check also works well in a scenario where a message travels through a number of components and returns to the sender. In this case, the Check Luggage component and the Content Enricher are local to the same component, and the data never has to travel across the network (see figure):
数据可以在本地存储和检索
The Data May Be Stored and Retrieved Locally
我们应该如何为数据选择密钥?我的脑海中浮现出许多选项:
How should we choose a key for the data? A number of options spring to mind:
业务密钥(例如客户 ID)可能已包含在消息正文中。
A business key, such as a customer ID, may already be contained in the message body.
该消息可以包含可用于将数据存储中的数据与该消息相关联的消息ID。
The message may contain a message ID that can be used to associate the data in the datastore with the message.
我们可以生成一个唯一的ID。
We can generate a unique ID.
重用现有的业务密钥似乎是最简单的选择。如果我们必须隐藏一些客户详细信息,我们可以稍后通过客户 ID 来引用它。当我们将此密钥传递给其他组件时,我们需要决定是否希望这些组件知道该密钥是客户 ID 而不仅仅是抽象密钥。将键表示为抽象键的优点是我们可以以相同的方式处理所有键,并且可以创建通用机制来基于抽象键从数据存储中检索数据。
Reusing an existing business key seems like the easiest choice. If we have to stow away some customer detail, we can reference it later by the customer ID. When we pass this key to other components, we need to decide whether we want these components to be aware that the key is a customer ID as opposed to just an abstract key. Representing the key as an abstract key has the advantage that we can process all keys in the same way and can create a generic mechanism to retrieve data from the datastore based on an abstract key.
使用消息 ID 作为密钥似乎很方便,但通常不是一个好主意。使用消息 ID 作为数据检索的密钥会导致双重语义附加到单个数据元素,并可能导致冲突。例如,假设我们需要将Claim Check引用传递给另一条消息。新消息应该被分配一个新的、唯一的 ID,但是我们不能再使用该新 ID 从数据存储中检索数据。仅当我们希望数据只能在单个消息的范围内访问时,消息 ID 的使用才有意义。因此,一般来说,最好分配一个新元素来保存密钥,并避免这种不良形式的“元素重用”。
Using the message ID as the key may seem convenient but is generally not a good idea. Using a message ID as a key for data retrieval results in dual semantics being attached to a single data element and can cause conflicts. For example, let's assume we need to pass the Claim Check reference on to another message. The new message is supposed to be assigned a new, unique ID, but then we can't use that new ID anymore to retrieve data from the datastore. The use of a message ID can be meaningful only in a circumstance where we want the data to be accessible only within the scope of the single message. Therefore, in general it is better to assign a new element to hold the key and avoid this bad form of "element reuse."
数据可能仅临时存储在数据存储中。我们如何删除未使用的数据?我们可以修改从数据存储中检索数据的语义,以便在读取数据时删除数据。在这种情况下,我们只能检索数据一次,出于安全原因,这在某些情况下实际上可能是可取的。但是,它不允许多个组件访问相同的数据。或者,我们可以为数据附加一个过期日期,并定义一个垃圾收集过程,定期删除超过一定期限的所有数据。作为第三种选择,我们可能不想删除任何数据。出现这种情况可能是因为我们使用业务系统作为数据存储(例如会计系统)并且需要维护该系统中的所有数据。
The data may be stored in the datastore only temporarily. How do we remove unused data? We can modify the semantics of the data retrieval from the datastore to delete the data when it is read. In this case, we can retrieve the data only once, which may actually be desirable in some cases for security reasons. However, it does not allow multiple components to access the same data. Alternatively, we can attach an expiration date to the data and define a garbage collection process that periodically removes all data over a certain age. As a third option, we may not want to remove any data. This may be the case because we use a business system as the datastore (e.g., an accounting system) and need to maintain all data in that system.
数据存储的实现可以采用多种形式。数据库是一个显而易见的选择,但一组 XML 文件或内存中的消息存储也可以用作数据存储。有时,我们可能会使用应用程序作为数据存储。重要的是,集成解决方案其他部分中的组件可以访问数据存储,以便这些部分可以重建原始消息。
The implementation of a datastore can take on various forms. A database is an obvious choice, but a set of XML files or an in-memory message store can serve as a datastore just as well. Sometimes, we may use an application as the datastore. It is important that the datastore is reachable by components in other parts of the integration solution so that these parts can reconstruct the original message.
虽然索赔检查的初衷除了避免发送大量数据之外,它还可以用于其他目的。通常,我们希望在向外部方发送消息之前删除敏感数据(见图)。这意味着外部各方仅在需要知道的基础上接收数据。例如,当我们将员工数据发送给外部方时,我们可能更愿意通过一些神奇的唯一 ID 来引用员工,并消除社会安全号码等字段。外部方完成所需的处理后,我们通过合并数据存储中的数据和外部方返回的消息来重建完整的消息。我们甚至可以为这些消息生成特殊的唯一密钥,以便我们限制外部方通过其拥有的密钥可以采取的操作。这将限制外部人员将消息恶意输入到我们的系统中。包含无效(或过期或已使用)密钥的消息将被阻止尝试使用错误密钥检索消息数据时内容丰富。
While the original intent of the Claim Check is to avoid sending around large volumes of data, it can also serve other purposes. Often, we want to remove sensitive data before sending a message to an outside party (see figure). This means that outside parties receive data only on a need-to-know basis. For example, when we send employee data to an external party, we may prefer to reference employees by some magic unique ID and eliminate fields such as social security number. After the outside party has completed the required processing, we reconstruct the complete message by merging data from the datastore and the message returned from the outside party. We may even generate special unique keys for these messages so that we restrict the actions the outside party can take by the key it possesses. This will restrict the outside party from maliciously feeding messages into our system. Messages containing an invalid (or expired or already used) key will be blocked by the Content Enricher when attempting to retrieve message data using the bad key.
消除可信进程边界之外的敏感消息数据
Eliminating Sensitive Message Data Outside of the Trusted Process Boundary
如果我们与多个外部方交互,流程管理器可以提供索赔检查的功能。当消息到达时,流程管理器创建流程实例(有时称为任务或作业)。流程管理器允许将附加数据与每个单独的流程实例相关联。实际上,流程引擎现在充当数据存储区,存储我们的消息数据。这使得流程管理器向外部各方发送仅包含与该方相关数据的消息。这些消息不必携带原始消息中包含的所有信息,因为该信息保存在进程的数据存储中。当流程管理器收到来自外部方的响应消息时,它将新数据与流程实例已存储的数据合并。
If we interact with more than one external party, a Process Manager can provide the function of a Claim Check. A Process Manager creates process instances (sometimes called tasks or jobs) when a message arrives. The Process Manager allows additional data to be associated with each individual process instance. In effect, the process engine now serves as the datastore, storing our message data. This allows the Process Manager to send messages to external parties that contain only the data relevant to that party. The messages do not have to carry all the information contained in the original message, since that information is kept with the process's datastore. When the Process Manager receives a response message from an external party, it merges the new data with the data already stored by the process instance.
在流程管理器中存储数据
Storing Data Inside a Process Manager
在企业对企业(B2B)集成场景中,企业接收来自不同业务伙伴的消息是很常见的。这些消息可能具有相同的含义,但遵循不同的格式,具体取决于合作伙伴的内部系统和偏好。例如,在 ThoughtWorks,我们为按次付费提供商构建了一个解决方案,该提供商必须接受和处理来自 1,700 多个附属机构的收视信息,其中大多数不符合标准格式。
In a business-to-business (B2B) integration scenario, it is quite common for an enterprise to receive messages from different business partners. These messages may have the same meaning but follow different formats, depending on the partners' internal systems and preferences. For example, at ThoughtWorks we built a solution for a pay-per-view provider that has to accept and process viewership information from over 1,700 affiliates, most of which did not conform to a standard format.
|
如何处理语义上相同但以不同格式到达的消息? How do you process messages that are semantically equivalent but arrive in a different format? |
从技术角度来看,最简单的解决方案似乎为所有参与者规定了统一的格式。如果企业是一家大公司并且拥有 B2B 交易或供应渠道的控制权,这可能会起作用。例如,如果通用汽车希望以通用消息格式接收来自其供应商的订单状态更新,我们可以非常确定乔的供应商业务很可能符合通用汽车的准则。然而,在许多其他情况下,企业不会有这样的奢侈。相反,许多业务模型将消息接收者定位为信息的“聚合器”,并且与各个参与者达成的协议的一部分是需要对其系统基础设施进行最少的更改。因此,
The easiest solution from a technical perspective may seem to dictate a uniform format on all participants. This may work if the business is a large corporation and has control over the B2B exchange or the supply channel. For example, if General Motors would like to receive order status updates from its suppliers in a common message format, we can be pretty sure that Joe's Supplier business is likely to conform to GM's guidelines. In many other situations, however, a business is not going to have such a luxury. On the contrary, many business models position the message recipient as an "aggregator" of information, and part of the agreement with the individual participants is that a minimum of changes is required to their systems infrastructure. As a result, you find the aggregator willing to process information arriving in any data format ranging from EDI records or comma-separated files to XML documents or Excel spreadsheets arriving via e-mail.
与众多合作伙伴打交道时的一个重要考虑因素是变化率。每个参与者不仅可能一开始就喜欢不同的数据格式,而且首选格式也可能随着时间的推移而改变。此外,新参与者可能会加入,而其他参与者可能会退出。即使特定合作伙伴每隔几年才对数据格式进行一次更改,与几十个合作伙伴打交道也可能很快导致每月或每周发生更改。重要的是尽可能将这些更改与其余处理隔离开来,以避免更改在整个系统中产生“连锁反应”。
One important consideration when dealing with a multitude of partners is the rate of change. Not only may each participant prefer a different data format to begin with, but the preferred format may also change over time. In addition, new participants may join while others drop off. Even if a specific partner makes changes to the data format only once every couple of years, dealing with a few dozen partners can quickly result in monthly or weekly changes. It is important to isolate these changes from the rest of the processing as much as possible to avoid a "ripple effect" of changes throughout the whole system.
为了将系统的其余部分与各种传入消息格式隔离,您需要将传入消息转换为通用格式。由于传入消息的类型不同,因此每种消息数据格式都需要不同的消息转换器。实现这一点的最简单方法是使用一组Datatype Channels ,每个消息类型对应一个 Datatype Channels 。然后,每个数据类型通道都连接到不同的消息转换器。这种方法的缺点是大量的消息格式会转化为同样大量的消息通道。
To isolate the remainder of the system from the variety of incoming message formats, you need to transform the incoming messages into a common format. Because the incoming messages are of different types, you need a different Message Translator for each message data format. The easiest way to accomplish this is to use a collection of Datatype Channels, one for each message type. Each Datatype Channel is then connected to a different Message Translator. The drawback of this approach is that a large number of message formats translates into an equally large number of Message Channels.
|
使用规范化器通过自定义消息转换器路由每种消息类型,以便生成的消息与通用格式匹配。 Use a Normalizer to route each message type through a custom Message Translator so that the resulting messages match a common format. |
规范化器为每种消息格式配备一个消息转换器,并通过消息路由器将传入消息路由到正确的消息转换器。
The Normalizer features one Message Translator for each message format and routes the incoming message to the correct Message Translator via a Message Router.
将消息路由到正确的消息转换器假定消息路由器可以检测传入消息的类型。许多消息传递系统在消息头中为每条消息配备了类型说明符字段,以使此类任务变得简单。然而,在许多 B2B 场景中,消息并不以符合企业内部消息传递系统的消息形式到达,而是以多种格式到达,例如逗号分隔的文件或没有关联模式的 XML 文档。虽然为任何传入数据格式配备类型说明符无疑是最佳实践,但我们非常清楚,世界远非完美。因此,我们需要考虑更通用的方法来识别传入消息的格式。无模式 XML 文档的一种常见方法是使用根元素的名称来假定正确的类型。如果多种数据格式使用相同的根元素,您可以使用 XPath 表达式来确定特定子节点的存在。以逗号分隔的文件可能需要更多的创造力。有时,您可以根据字段数量和数据类型(例如数字与字符串)来确定类型。如果数据以文件形式到达,最简单的方法可能是使用文件名或文件夹结构作为替代数据类型 通道。每个业务合作伙伴都可以使用唯一的命名约定来命名该文件。然后,消息路由器可以使用该文件名将消息路由到适当的消息转换器。
Routing the message to the correct Message Translator assumes that the Message Router can detect the type of the incoming message. Many messaging systems equip each message with a type specifier field in the message header to make this type of task simple. However, in many B2B scenarios, messages do not arrive as messages compliant with the enterprise's internal messaging system, but in diverse formats such as comma-separated files or XML documents without associated schema. While it is certainly best practice to equip any incoming data format with a type specifier, we know all too well that the world is far from perfect. As a result, we need to think of more general ways to identify the format of the incoming message. One common way for schema-less XML documents is to use the name of the root element to assume the correct type. If multiple data formats use the same root element, you can use XPath expressions to determine the presence of specific subnodes. Comma-separated files can require a little more creativity. Sometimes you can determine the type based on the number of fields and the type of the data (e.g., numeric vs. string). If the data arrives as files, the easiest way may be to use the file name or the file folder structure as a surrogate Datatype Channel. Each business partner can name the file with a unique naming convention. The Message Router can then use the file name to route the message to the appropriate Message Translator.
使用消息路由器还允许将相同的转换用于多个业务合作伙伴。如果多个业务合作伙伴使用相同的格式或者转换足够通用以适应多种消息格式,这可能会很有用。例如,XPath 表达式非常适合从 XML 文档中挑选元素,即使文档的格式有所不同。
The use of a Message Router also allows the same transformation to be used for multiple business partners. That might be useful if multiple business partners use the same format or if a transformation is generic enough to accommodate multiple message formats. For example, XPath expressions are great at picking out elements from XML documents even if the documents vary in format.
由于规范化器在消息传递解决方案中很常见,因此我们为其创建了一个简写图标:
Since a Normalizer is a common occurrence in messaging solutions, we created a shorthand icon for it:
规范化器的实际应用
The Normalizer in Action
我们正在设计几个通过消息传递协同工作的应用程序。 每个应用程序都有自己的内部数据格式。
We are designing several applications to work together through Messaging. Each application has its own internal data format.
|
在集成使用不同数据格式的应用程序时,如何最大限度地减少依赖性? How can you minimize dependencies when integrating applications that use different data formats? |
独立开发的应用程序往往使用不同的数据格式,因为每种格式的设计都只考虑了该应用程序。当应用程序被设计为向某个未知应用程序发送消息或从某些未知应用程序接收消息时,该应用程序自然会使用对其最方便的消息格式。同样,用于集成打包应用程序的商业适配器通常以类似于应用程序内部数据结构的数据格式发布和使用消息。
Independently developed applications tend to use different data formats because each format was designed with just that application in mind. When an application is designed to send messages to or receive messages from some unknown application, the application will naturally use the message format that is most convenient for it. Likewise, commercial adapters used to integrate packaged applications typically publish and consume messages in a data format that resembles the application's internal data structure.
消息转换器解决消息格式的差异,而无需更改应用程序或让应用程序了解彼此的数据格式。但是,如果大量应用程序相互通信,则每对通信应用程序之间可能需要一个消息转换器(见图)。
The Message Translator resolves differences in message formats without changing the applications or having the applications know about each other's data formats. However, if a large number of applications communicate with each other, one Message Translator may be needed between each pair of communicating applications (see figure).
随着系统数量的增加,连接数量呈爆炸式增长
The Number of Connections Explodes with an Increasing Number of Systems
这种方法需要大量的消息转换器,特别是考虑到每个集成应用程序可能发布或使用多种消息类型时。所需消息转换器的数量随着集成应用程序的数量呈指数级增长,这很快就会变得难以管理。
This approach requires a large number of Message Translators, especially when considering that each integrated application may publish or consume multiple message types. The number of required Message Translators increases exponentially with the number of integrated applications, which quickly becomes unmanageable.
虽然消息转换器提供了两个通信应用程序使用的消息格式之间的间接转换,但它仍然依赖于任一应用程序使用的消息格式。因此,如果应用程序的数据格式发生更改,则更改的应用程序和与之通信的所有其他应用程序之间的所有消息转换器都必须更改。同样,如果将新应用程序添加到解决方案中,则必须从每个现有应用程序到新应用程序创建新的消息转换器,以便交换消息。这种情况造成了一场噩梦,因为必须维护所有消息转换器。
While the Message Translator provides an indirection between the message formats used by two communicating applications, it is still dependent on the message formats used by either application. As a result, if an application's data format changes, all Message Translators between the changing application and all other applications that it communicates with have to change. Likewise, if a new application is added to the solution, new Message Translators have to be created from each existing application to the new application in order to exchange messages. This situation creates a nightmare out of having to maintain all Message Translators.
我们还需要记住,注入消息流的每个额外转换步骤都会增加延迟并降低消息吞吐量。
We also need to keep in mind that each additional transformation step injected into a message flow can increase latency and reduce message throughput.
|
设计独立于任何特定应用程序的规范数据模型。要求每个应用程序以这种通用格式生成和使用消息。 Design a Canonical Data Model that is independent from any specific application. Require each application to produce and consume messages in this common format. |
规范数据模型在应用程序的各个数据格式之间提供了额外的间接级别。如果将新应用程序添加到集成解决方案中,则只需创建规范数据模型之间的转换,无论已参与的应用程序数量如何。
The Canonical Data Model provides an additional level of indirection between applications' individual data formats. If a new application is added to the integration solution, only transformation between the Canonical Data Model has to be created, regardless of the number of applications that already participate.
如果只有少量应用程序参与集成解决方案,则规范数据模型的使用可能看起来过于复杂。然而,随着应用程序数量的增加,该解决方案很快就会得到回报。如果我们假设每个应用程序向其他应用程序发送消息并从其他应用程序接收消息,那么如果我们直接在应用程序的数据格式之间进行转换,则由两个应用程序组成的解决方案将只需要两个消息转换器,而规范数据模型需要四个消息转换器。 由三个应用程序组成的解决方案需要六个消息转换器无论采用哪种方法。然而,由六个应用程序组成的解决方案在没有规范数据模型的情况下需要 30 (!) 个消息转换器,而在使用规范数据模型时仅需要 12 个消息转换器。
The use of a Canonical Data Model may seem overly complicated if only a small number of applications participate in the integration solution. However, the solution quickly pays off as the number of applications increases. If we assume that each application sends and receives messages to and from each other application, a solution consisting of two applications would require only two Message Translators if we translate between the applications' data formats directly, whereas the Canonical Data Model requires four Message Translators. A solution consisting of three applications requires six Message Translators with either approach. However, a solution consisting of six applications requires 30 (!) Message Translators without a Canonical Data Model and only 12 Message Translators when using a Canonical Data Model.
如果现有应用程序将来可能被另一个应用程序取代,规范数据模型也可能非常有用。例如,如果许多应用程序与将来可能被新系统取代的遗留系统接口,那么如果构建了规范数据模型的概念,从一个应用程序切换到另一个应用程序的工作量就会大大减少进入原溶液。
The Canonical Data Model can also be very useful if an existing application is likely to be replaced by another application in the future. For example, if a number of applications interface with a legacy system that is likely to be replaced by a new system in the future, the effort of switching from one application to the other is much reduced if the concept of a Canonical Data Model is built into the original solution.
如何使应用程序符合通用格式?您有三个基本选择:
How do you make applications conform to the common format? You have three basic choices:
更改应用程序的内部数据格式。 这在理论上是可能的,但在复杂的现实场景中不太可能。如果很容易让每个应用程序本机使用相同的数据格式,那么我们最好使用共享数据库而不是消息传递。
Change the applications' internal data format. This may be possible in theory, but it is unlikely in a complex, real-life scenario. If it was easy to just make each application to natively use the same data format, we would be better off using Shared Database instead of Messaging.
Implement a Messaging Mapper inside the application. Custom applications can use a mapper to generate the desired data format.
使用外部消息转换器。 您可以使用外部消息转换器将特定于应用程序的消息格式转换为规范。这可能是转换打包应用程序数据的唯一选择。
Use an external Message Translator. You can use an external Message Translator to translate from the application-specific message format into the format specified by the Canonical Data Model. This may be your only option for transforming a packaged application's data.
是否使用消息传递映射器或外部消息转换器取决于转换的复杂性和应用程序的可维护性。打包的应用程序通常不需要使用消息传递映射器,因为源代码不可用。对于自定义应用程序,选择取决于转换的复杂性。许多集成工具套件提供可视化转换编辑器,可以更快地构建映射规则。然而,如果转换很复杂,这些可视化工具可能会变得笨拙。
Whether to use a Messaging Mapper or an external Message Translator depends on the complexity of the transformation and the maintainability of the application. Packaged applications usually eliminate the use of a Messaging Mapper because the source code is not available. For custom applications the choice depends on the complexity of the transformation. Many integration tool suites provide visual transformation editors that allow faster construction of mapping rules. However, these visual tools can get unwieldy if transformations are complex.
当使用外部消息转换器时,我们需要区分公共(规范)消息和私有(特定于应用程序)消息。应用程序及其关联的消息转换器之间的消息被视为私有,因为其他应用程序不应使用这些消息。一旦消息翻译器将消息转换为符合规范数据模型的格式,该消息就被认为是公开的并且可以被其他系统使用。
When using an external Message Translator, we need to distinguish between public (canonical) messages and private (application-specific) messages. The messages between an application and its associated Message Translator are considered private because no other application should be using these messages. Once the Message Translator transforms the message into a format compliant with the Canonical Data Model, the message is considered public and can be used by other systems.
规范数据模型的使用确实会给消息流带来一定量的开销。现在,每条消息都必须经历两个转换步骤,而不是一个:一个从源应用程序的格式转换为通用格式,一个从通用格式转换为目标应用程序的格式。因此,规范数据模型的使用有时被称为双重转换(直接从一个应用程序的格式转换为另一种应用程序的格式称为直接转换))。每个转换步骤都会导致消息流中出现额外的延迟。因此,对于吞吐量非常高的系统,直接翻译可能是唯一的选择。可维护性和性能之间的这种权衡很常见。最好的建议是使用更易于维护的解决方案(即规范数据模型),除非性能要求不允许。一个缓解因素可能是许多翻译是无状态的,因此有助于与并行执行的多个消息翻译器进行负载平衡。
The use of a Canonical Data Model does introduce a certain amount of overhead into the message flow. Each message now has to undergo two translation steps instead of one: one translation from the source application's format into the common format and one from the common format into the target application's format. For this reason, the use of a Canonical Data Model is sometimes referred to as double translation (transforming directly from one application's format to the other is called direct translation). Each translation step causes additional latency in the flow of messages. Therefore, for very high throughput systems, direct translation can be the only choice. This trade-off between maintainability and performance is common. The best advice is to use the more maintainable solution (i.e., the Canonical Data Model) unless performance requirements do not allow it. A mitigating factor may be that many translations are stateless and therefore lend themselves to load balancing with multiple Message Translators executing in parallel.
设计规范数据模型可能很困难;大多数企业都至少有过一次失败的“企业数据模型”尝试。为了实现平衡的模型,设计人员应努力使统一模型对于所有集成的应用程序同样有效。不幸的是,在实践中,这个理想很难实现。考虑到规范数据模型不必对所有应用程序内使用的完整数据集进行建模,而只需对参与消息传递的部分进行建模(见图),成功设计规范数据模型的机会就会提高。这可以显着降低创建规范数据模型的复杂性。
Designing a Canonical Data Model can be difficult; most enterprises have at least one failed "enterprise data model" effort under their belt. To achieve a balanced model, designers should strive to make the unified model work equally well for all applications being integrated. Unfortunately, in practice, this ideal is difficult to achieve. The chances of successfully designing a Canonical Data Model improve when considering that the Canonical Data Model does not have to model the complete set of data used inside all applications, but only the portion that participates in messaging (see figure). This can significantly reduce the complexity of creating the Canonical Data Model.
仅对相关数据建模
Modeling Only the Relevant Data
使用规范数据模型还可以带来政治优势。使用规范数据模型允许开发人员和业务用户根据公司的业务领域而不是特定的包实现来讨论集成解决方案。例如,打包的应用程序可以以许多不同的内部格式表示客户的共同概念,例如“帐户”、“付款人”和“联系人”。定义规范数据模型通常是解决应用程序之间语义不一致情况的第一步(请参阅 [ Kent ])。
Using a Canonical Data Model can also have political advantages. Using a Canonical Data Model allows developers and business users to discuss the integration solution in terms of the company's business domain, not a specific package implementation. For example, packaged applications may represent the common concept of a customer in many different internal formats, such as "account," "payer," and "contact." Defining a Canonical Data Model is often the first step to resolving cases of semantic dissonance between applications (see [Kent]).
该模式开头的图显示了每个应用程序之间转换所需的大量转换器,看起来与 Message Broker中显示的图惊人地相似。这提醒我们应用程序之间的依赖关系可以存在于多个级别。消息通道的使用提供了应用程序之间的公共传输层,并消除了应用程序的各个传输协议之间的依赖性。消息路由器可以提供位置无关性,以便发送应用程序不必依赖于接收应用程序的位置。使用通用数据表示(例如 XML)消除了对任何特定于应用程序的数据类型的依赖。最后,规范数据模型解决了对应用程序使用的数据格式和语义的依赖关系。
The figure at the beginning of this pattern showing the large number of transformers needed to translate between each and every application looks surprisingly similar to the figure shown in the Message Broker. This reminds us that dependencies between applications can exist at multiple levels. The use of Message Channels provides a common transport layer between applications and removes dependencies between applications' individual transport protocols. Message Routers can provide location-independence so that a sending application does not have to depend on the location of the receiving application. The use of a common data representation such as XML removes dependencies on any application-specific data types. Finally, the Canonical Data Model resolves dependencies on the data formats and semantics used by the applications.
一如既往,唯一不变的就是变化。因此,符合规范数据模型的消息应指定格式指示符。
As always, the only constant is change. Therefore, messages conforming to the Canonical Data Model should specify a Format Indicator.
|
示例: WSDL Example: WSDL 当从应用程序访问外部服务时,该服务可能已经指定要使用的规范数据模型。在 XML Web 服务领域,数据格式由 WSDL(Web 服务定义语言;请参阅 [ WSDL 1.1 ])文档指定。WSDL 指定服务可以使用和生成的请求和回复消息的结构。在大多数情况下,WSDL 中指定的数据格式与提供服务的应用程序的内部格式不同。实际上,WSDL 指定了参与对话的双方都可以使用的规范数据模型。双重翻译由消息映射器或服务使用者中的消息网关和服务提供者中的EAA ] 。 When accessing an external service from your application, the service may already specify a Canonical Data Model to be used. In the world of XML Web services, the data format is specified by a WSDL (Web Services Definition Language; see [WSDL 1.1]) document. The WSDL specifies the structure of request and reply messages that the service can consume and produce. In most cases, the data format specified in the WSDL is different than the internal format of the application providing the service. Effectively, the WSDL specifies a Canonical Data Model to be used by both parties participating in the conversation. The double translation consists of a Messaging Mapper or a Messaging Gateway in the service consumer and a Remote Facade [EAA] in the service provider. |
|
示例: TIBCO ActiveEnterprise Example: TIBCO ActiveEnterprise 许多 EAI 工具套件提供了一整套工具来定义和描述规范数据模型。例如,TIBCO ActiveEnterprise 套件提供了 TIB/Designer,允许用户检查所有常见消息定义。消息定义可以从 XML 模式定义导入或导出到 XML 模式定义。当使用内置可视化工具集实现消息转换器时,该工具向设计人员提供特定于应用程序的数据格式和存储在中央数据格式存储库中的规范数据模型。这简化了两种数据格式之间消息转换器的配置。 Many EAI tool suites provide a complete set of tools to define and describe the Canonical Data Model. For example, the TIBCO ActiveEnterprise suite provides the TIB/Designer that allows the user to inspect all common message definitions. Message definitions can be imported from or exported to XML schema definitions. When implementing a Message Translator using a built-in visual tool set, the tool presents the designer with both the application-specific data format and the Canonical Data Model stored in the central data format repository. This simplifies the configuration of the Message Translator between the two data formats. TIBCO Designer:维护规范数据模型的 GUI 工具 The TIBCO Designer: A GUI Tool to Maintain a Canonical Data Model |
本章演示如何将路由和转换模式组合成更大的解决方案。作为示例场景,我们选择对消费者从多家银行获取贷款报价的过程进行建模。我们冒昧地稍微简化了业务流程,这样我们就可以专注于集成模式的讨论,而不是举办消费者金融服务的讲座。根据我们定义的模式,我们使用不同的编程语言、技术和消息传递模型讨论并为此过程创建三种替代实现。
This chapter demonstrates how to compose routing and transformation patterns into a larger solution. As an example scenario, we chose to model the process of a consumer obtaining quotes for a loan from multiple banks. We took the liberty to simplify the business process a little bit so we can focus on a discussion of integration patterns as opposed to holding a lecture in consumer financial services. Based on the patterns that we defined, we discuss and create three alternative implementations for this process, using different programming languages, technologies, and messaging models.
在购买贷款时,客户通常会致电多家银行以寻找最优惠利率的交易。每家银行都会询问客户的社会安全号码、贷款金额和所需期限(即还清贷款之前的月数)。然后,每家银行通常会联系信贷机构来调查客户的信用背景。根据所要求的条款和客户的信用记录,银行向消费者回复利率报价(或者庆幸地拒绝)。一旦客户收到所有银行的报价,他或她就可以选择利率最低的最佳报价。
When shopping for a loan, a customer usually calls several banks to find the deal with the best possible interest rate. Each bank asks the customer for his or her social security number, the amount of the loan, and the desired term (i.e., the number of months until the loan has to be paid off). Each bank then investigates the customer's credit background, usually by contacting a credit agency. Based on the requested terms and the customer's credit history, the bank responds with an interest rate quote to the consumer (or declines thankfully). Once the customer has received quotes from all banks, he or she can then select the best offer with the lowest interest rate.
消费者与银行交谈以获得贷款报价
A Consumer Talking to Banks to Get a Loan Quote
由于联系多家银行提出贷款报价请求是一项繁琐的任务,因此贷款经纪人向消费者提供这项服务。贷款经纪人通常不隶属于任何一家银行,但可以接触许多贷款机构。经纪人收集客户数据一次并联系信用机构以获取客户的信用记录。根据信用评分和历史记录,经纪人向最适合满足客户标准的多家银行提出请求。经纪人从银行收集报价结果,并选择最佳报价传回给消费者。
Because contacting multiple banks with a loan quote request is a tedious task, loan brokers offer this service to consumers. A loan broker is typically not affiliated with any one bank but has access to many lending institutions. The broker gathers the customer data once and contacts the credit agency to obtain the customer's credit history. Based on the credit score and history, the broker presents the request to a number of banks that are best suited to meet the customer's criteria. The broker gathers the resulting quotes from the banks and selects the best offer to pass back to the consumer.
充当中介的贷款经纪人
A Loan Broker Acting as Intermediary
我们希望使用前面章节中讨论的集成模式来设计一个贷款经纪人系统。为此,我们首先列出贷款经纪人需要执行的各项任务。
We want to design a loan broker system using integration patterns discussed in the previous chapters. To do this, let's first list the individual tasks that the loan broker needs to perform.
接收消费者的贷款报价请求。
Receive the consumer's loan quote request.
从信用机构获取信用评分和历史记录。
Obtain credit score and history from credit agency.
确定最合适的联系银行。
Determine the most appropriate banks to contact.
向每个选定的银行发送请求。
Send a request to each selected bank.
收集每个选定银行的答复。
Collect responses from each selected bank.
确定最佳响应。
Determine the best response.
将结果返回给消费者。
Pass the result back to the consumer.
让我们看看哪些模式可以帮助我们设计和实现贷款经纪人。第一步描述代理如何接收传入请求。我们在第 10 章“消息传递端点”中更详细地介绍了这个主题,因此现在,让我们跳过这一步并假设消息以某种方式被代理接收。接下来,经纪人必须检索一些附加信息:客户的信用评分。内容丰富器听起来是这项任务的理想选择。一旦代理获得完整信息,代理必须确定将请求消息路由到的适当银行。我们可以通过另一个内容丰富器来实现这一点计算请求的收件人列表。将请求消息发送给多个接收者并将响应重新组合回单个消息是Scatter-Gather的特点。Scatter -Gather可以使用发布-订阅通道或接收者列表将请求发送到银行。一旦银行回复其利率报价,Scatter-Gather就会使用聚合器将各个利率报价聚合为单个报价,供消费者使用。 如果我们使用这些模式对消息流进行建模,我们将得到以下设计:
Let's see which patterns could help us design and implement the loan broker. The first step describes how the broker receives the incoming request. We cover this topic in much more detail in Chapter 10, "Messaging Endpoints," so for now, let's skip over this step and assume the message is somehow received by the broker. Next, the broker has to retrieve some additional information: the customer's credit score. A Content Enricher sounds like the ideal choice for this task. Once the broker has the complete information, the broker must determine the appropriate banks to route the request message to. We can accomplish this with another Content Enricher that computes the list of recipients for the request. Sending a request message to multiple recipients and recombining the responses back into a single message is the specialty of the Scatter-Gather. The Scatter-Gather can use a Publish-Subscribe Channel or a Recipient List to send the request to the banks. Once the banks reply with their rate quotes, the Scatter-Gather aggregates the individual rate quotes into a single quote for the consumer using an Aggregator. If we model the message flow using these patterns, we arrive at the following design:
简单的贷款经纪人设计
Simple Loan Broker Design
我们尚未考虑到每家银行可能会使用略有不同的消息格式来处理贷款请求和响应的可能性。因为我们希望将路由和聚合逻辑与银行的专有格式分开,所以我们需要将消息转换器插入经纪人和银行之间的通信线路中。我们可以使用规范化器将各个响应转换为通用格式:
We have not yet accounted for the likely event that each bank may use a slightly different message format for the loan request and response. Because we want to separate the routing and aggregation logic from the banks' proprietary formats, we need to insert Message Translators into the communication lines between the broker and the banks. We can use a Normalizer to translate the individual responses into a common format:
完整的贷款经纪人设计
Complete Loan Broker Design
到目前为止,我们已经描述了消息流以及可用于描述贷款经纪人组件设计的路由和转换模式。我们还没有讨论经纪人操作的时间。我们有两个主要选择:
So far, we have described the flow of the messages and the routing and transformation patterns we can use to describe the design of the loan broker component. We have not yet discussed the timing of the broker operation. We have two primary choices:
同步(顺序): 经纪商向一家银行询问报价并等待响应,然后再联系下一家银行。
Synchronous (Sequential): The broker asks one bank for the quote and waits for a response before contacting the next bank.
异步(并行): 经纪商立即发送所有报价请求并等待答案返回。
Asynchronous (Parallel): The broker sends all quote requests at once and waits for the answers to come back.
我们可以使用 UML 序列图来说明这两个选项。同步选项意味着按顺序处理所有贷款请求(参见下图)。该解决方案的优点是更易于管理,因为我们不必处理任何并发问题或线程。然而,这是一个低效的解决方案,因为我们没有利用每个银行拥有独立处理能力并且可以同时执行请求的事实。结果,消费者可能需要等待很长时间才能得到答复。
We can use UML sequence diagrams to illustrate the two options. The synchronous option implies a sequential processing of all loan requests (see the following figure). This solution has the advantage of being simpler to manage because we do not have to deal with any concurrency issues or threads. However, it is an inefficient solution because we do not take advantage of the fact that each bank possesses independent processing power and could be executing requests simultaneously. As a result, the consumer might have to wait a long time for an answer.
同步、顺序处理贷款请求
Synchronous, Sequential Processing of Loan Requests
异步解决方案立即发出所有请求,以便每个银行都可以开始处理。当银行完成计算后,他们将结果返回给贷款经纪人。该解决方案可以实现更快的响应。例如,如果所有银行花费相似的时间来生成贷款报价,则此解决方案几乎快n倍,其中n是我们正在处理的银行数量。贷款经纪人现在必须能够以任何顺序接受贷款报价回复消息,因为不能保证响应到达的顺序与发出请求的顺序相同。以下序列图说明了此选项。贷款报价请求上的空心箭头表示异步调用。
The asynchronous solution issues all requests right away so that each bank can start processing. As the banks finish the computation, they return the results to the loan broker. This solution allows for a much quicker response. For example, if all banks take a similar amount of time to produce a loan quote, this solution is almost n times faster, where n is the number of banks we are dealing with. The loan broker now must be able to accept the loan quote reply messages in any order, because there is no guarantee that responses arrive in the same order that requests were made. The following sequence diagram illustrates this option. The open arrowhead on a loan quote request indicates an asynchronous invocation.
贷款请求的异步并行处理
Asynchronous, Parallel Processing of Loan Requests
通过消息队列使用异步调用的另一个显着优点是能够创建多个服务提供者实例。例如,如果事实证明信用局是一个瓶颈,我们可以决定运行该组件的两个实例。由于贷款经纪人将请求消息发送到队列而不是直接发送到信用局组件,因此只要将响应放回响应通道,哪个组件实例处理该消息并不重要。
Another significant advantage of using asynchronous invocation via a message queue is the ability to create more than one instance of a service provider. For example, if it turns out that the credit bureau is a bottleneck, we could decide to run two instances of that component. Because the loan broker sends the request message to a queue instead of directly to the credit bureau component, it does not matter which component instance processes the message as long as the response is put back onto the response channel.
使用Scatter-Gather来获取最佳报价允许我们从两种寻址机制中进行选择:接收者列表或发布-订阅通道。该决定主要取决于我们希望对被允许参与特定贷款请求的银行施加多少控制。同样,我们有多种选择:
Using a Scatter-Gather to obtain the best quote allows us to choose from two addressing mechanisms, a Recipient List or a Publish-Subscribe Channel. The decision primarily depends on how much control we want to exert over the banks who are allowed to participate in a specific loan request. Again, we have a number of choices:
已修复: 银行列表是硬编码的。每个贷款请求都会发送给同一组银行。
Fixed: The list of banks is hard-coded. Each loan request goes to the same set of banks.
分配: 经纪商维护哪些银行适合特定请求的标准。例如,它不会向专门针对优质客户的银行发送信用记录不良的客户的报价请求。
Distribution: The broker maintains criteria on which banks are a good match for a specific request. For example, it would not send a quote request for a customer with a poor credit history to a bank that specializes in premier customers.
拍卖: 代理使用发布-订阅通道广播请求。 任何有兴趣的银行都可以订阅该频道并根据请求“出价”。银行可以随意认购或退订。每家银行仍然可以采用自己的标准来决定是否提交投标。
Auction: The broker broadcasts the request using a Publish-Subscribe Channel. Any bank that is interested is allowed to subscribe to the channel and "bid" on the request. Banks can subscribe or unsubscribe at will. Each bank can still apply its own criteria on whether to submit a bid.
哪个选项最适合我们的场景?与往常一样,不存在简单的“答案是......”,但选择是由业务和技术偏好和约束驱动的。第一个选项很简单,让经纪商控制银行列表。然而,如果新银行频繁进出,该解决方案可能会带来管理负担。此外,银行可能不乐意收到一堆不相关的请求,因为每个请求都会给银行带来一定的内部成本。此外,为了保持这种方法的简单性,聚合策略更有可能要求所有银行提交响应。银行可能希望保留拒绝投标的权利。
Which option is best for our scenario? As always, there is no simple "and the answer is...," but the choice is driven by both business and technical preferences and constraints. The first option is simple and gives the broker control over the list of banks. However, if new banks come and go frequently, the solution can result in an administrative burden. Also, the bank may not be happy to be receiving a bunch of irrelevant requests, because each request incurs a certain internal cost for the bank. Also, to maintain the simplicity of this approach, the aggregation strategy is more likely to require all banks to submit a response. Banks may want to reserve the right to withhold from the bid.
分配方法(使用接收者列表)使经纪人可以更好地控制每个贷款请求涉及哪家银行。这使得代理可以通过减少请求数量来提高效率。它还允许经纪人根据业务关系优先选择某些银行。不利的一面是,它需要在贷款经纪人组件内实现和维护额外的业务逻辑。分发和固定方法都需要为每个参与者提供单独的消息通道,以便控制消息流。
A distribution approach (using a Recipient List) gives the broker more control over which bank to involve in each loan request. This allows the broker to be more efficient by reducing the number of requests. It also allows the broker to prefer certain banks based on the business relationship. On the downside, it requires additional business logic to be implemented and maintained inside the loan broker component. Both the distribution and the fixed approach require a separate Message Channel for each participant in order to control the message flow.
使用发布-订阅通道向所有订阅银行广播贷款请求,并让每个银行确定要服务的请求。每个银行都可以使用消息过滤器或实施选择性消费者来过滤掉不需要的贷款请求。这种方法使得贷款经纪人在添加或删除银行的情况下几乎不需要维护,但需要银行方面做更多的工作。该解决方案仅需要一个消息通道,但必须将其实现为发布-订阅通道。许多高效的发布-订阅方案使用通常不跨广域网或 Internet 进行路由的 IP 多播。其他实现通过使用点对点和接收者列表来模拟发布-订阅通道。这种方法保留了发布-订阅通道的简单语义,但在使用通道和网络带宽方面效率较低。有关路由和发布-订阅加过滤之间的其他权衡,请参阅消息过滤器的描述。
Using a Publish-Subscribe Channel broadcasts a loan request to all subscribing banks and lets each bank determine which requests to service. Each bank can use a Message Filter or implement a Selective Consumer to filter out undesirable loan requests. This approach renders the loan broker pretty much maintenance-free in cases of adding or removing banks, but it requires more work on the side of the banks. This solution requires only a single Message Channel , but it has to be implemented as a Publish-Subscribe Channel. Many efficient publish-subscribe schemes use IP Multicast that typically does not route across wide-area networks or the Internet. Other implementations emulate a Publish-Subscribe Channel by using an array of Point-to-Point Channels and a Recipient List. This approach preserves the simple semantics of a Publish-Subscribe Channel but is less efficient in its use of channels and network bandwidth. For additional trade-offs between routing and publish-subscribe plus filtering, see the description of the Message Filter.
当收到银行的贷款报价时,我们有类似的设计选择。我们可以让所有银行将其答复提交到单个答复渠道,或者我们可以为每家银行设立单独的答复渠道。使用单一通道减少了为每个参与银行设置单独通道的维护负担,但要求每个银行的回复消息包含一个用于标识发出报价的银行的字段。如果我们使用单个响应通道,聚合器可能不知道需要多少响应消息,除非接收者列表将此信息传递给聚合器(我们称之为初始化聚合器) 。如果我们使用拍卖方式发布-订阅通道,贷款经纪人不知道可能的响应数量,因此聚合器必须采用不依赖于参与者总数的完整性条件。例如,聚合器可以但如果暂时只有两家银行参与,即便如此也会存在风险。在这种情况下,聚合器可能会
When receiving loan quotes from the bank, we have similar design choices. We can have all banks submit their responses to a single response channel, or we can have a separate response channel for each bank. Using a single channel reduces the maintenance burden of setting up a separate channel for each participating bank but requires each bank's reply message to include a field identifying the bank who issued the quote. If we use a single response channel, the Aggregator may not know how many response messages to expect unless the Recipient List passes this information to the Aggregator (we call this an initialized Aggregator). If we use an auction-style Publish-Subscribe Channel , the number of possible responses is unknown to the loan broker, so the Aggregator has to employ a completeness condition that does not depend on the total number of participants. For example, the Aggregator could simply wait until it has a minimum of three responses. But even that would be risky if temporarily only two banks participate. In that case, the Aggregator could time out and report that it received an insufficient number of responses.
诸如贷款经纪人之类的服务应该能够处理想要同时使用该服务的多个客户。例如,如果我们将贷款经纪人功能公开为 Web 服务或将其连接到公共网站,我们实际上无法控制客户端的数量,并且可能会收到数百或数千个并发请求。我们可以让贷款经纪人使用两种不同的策略来处理多个并发请求:
A service such as a loan broker should be able to deal with multiple clients wanting to use the service concurrently. For example, if we expose the loan broker function as a Web service or connect it to a public Web site, we do not really have any control over the number of clients and we may receive hundreds or thousands of concurrent requests. We can enable the loan broker to process multiple concurrent requests using two different strategies:
执行多个实例。
Execute multiple instances.
单个事件驱动实例。
A single event-driven instance.
第一个选项维护贷款经纪人组件的多个并行实例。我们可以为每个传入请求启动一个新实例,或者维护一个活动贷款经纪人进程的“池”,并将传入请求分配给下一个可用进程(使用消息调度程序))。如果没有可用的进程,我们会将请求排队,直到有可用的进程。进程池的优点是我们可以以可预测的方式分配系统资源。例如,我们可以决定最多执行 20 个贷款经纪人实例。相反,如果我们为每个请求启动一个新进程,那么如果并发请求激增,我们可能会很快阻塞机器。此外,维护正在运行的进程池允许我们为多个请求重用现有进程,从而节省进程实例化和初始化的时间。
The first option maintains multiple parallel instances of the loan broker component. We can either start a new instance for each incoming request or maintain a "pool" of active loan broker processes and assign incoming requests to the next available process (using a Message Dispatcher). If no process is available, we would queue up the requests until a process becomes available. A process pool has the advantage that we can allocate system resources in a predictable way. For example, we can decide to execute a maximum of 20 loan broker instances. In contrast, if we started a new process for each request, we could quickly choke the machine if a spike of concurrent requests arrives. Also, maintaining a pool of running processes allows us to reuse an existing process for multiple requests, saving time for process instantiation and initialization.
由于贷款经纪人所需的大部分处理都是等待外部各方(信用局和银行)的答复,因此运行许多并行进程可能无法很好地利用系统资源。相反,我们可以运行一个流程实例,在传入消息事件到达时对其做出反应。处理单个消息(例如,银行报价)是一项相对简单的任务,因此单个进程可能能够为许多并发请求提供服务。这种方法可以更有效地使用系统资源并简化解决方案的管理,因为我们只需监视单个流程实例。潜在的缺点是可扩展性有限,因为我们被绑定到一个进程。许多大容量应用程序结合使用这两种技术,
Because much of the processing required by the loan broker is to wait for replies from external parties (the credit bureau and the banks), running many parallel processes may not be a good use of system resources. Instead, we can run a single process instance that reacts to incoming message events as they arrive. Processing an individual message (e.g., a bank quote) is a relatively simple task, so that a single process may be able to service many concurrent requests. This approach uses system resources more efficiently and simplifies management of the solution, since we have to monitor only a single process instance. The potential downside is the limited scalability because we are tied to one process. Many high-volume applications use a combination of the two techniques, executing multiple parallel processes, each of which can handle multiple requests concurrently.
执行多个并发请求需要我们将系统中的每条消息关联到正确的流程实例。例如,对于银行来说,将所有回复消息发送到固定通道可能是最方便的。这意味着回复通道可以包含与不同客户的并发报价请求相关的消息。因此,我们需要为每条消息配备一个关联标识符,以识别银行正在响应哪个客户请求。
Executing multiple concurrent requests requires us to associate each message in the system to the correct process instance. For example, it may be most convenient for a bank to send all reply messages to a fixed channel. This means that the reply channel can contain messages related to different customers' concurrent quote requests. Therefore, we need to equip each message with a Correlation Identifier to identify which customer request the bank is responding to.
为了实现贷款经纪人示例,我们需要做出三个主要设计决策:我们必须为请求选择排序方案,为银行选择寻址方案,并定义聚合策略。此外,我们还必须选择一种编程语言和消息传递基础设施。总的来说,这些单独的选项会导致大量潜在的实施选择。我们选择实施三种代表性解决方案,以突出不同实施选项之间的主要权衡。与本书中的所有示例一样,具体技术的选择有些随意,并不表明特定供应商技术的优越性。下表重点介绍了每种解决方案的特点:
In order to implement the loan broker example, we have three main design decisions to make: We have to select a sequencing scheme for the requests, select an addressing scheme for the banks, and define an aggregation strategy. In addition, we have to select a programming language and a messaging infrastructure. In aggregate, these individual options result in a large number of potential implementation choices. We chose to implement three representative solutions to highlight the main trade-offs between the different implementation options. As with all examples in this book, the choice of specific technologies is somewhat arbitrary and does not indicate the superiority of a specific vendor's technology. The following table highlights the characteristics of each solution:
执行 Implementation | 测序 Sequencing | 寻址 Addressing | 聚合 Aggregation | 通道类型 Channel Type | 产品技术 Product Technology |
|---|---|---|---|---|---|
A A | 同步 Synchronous | 分配 Distribution | 渠道 Channel | 网络服务/SOAP Web Service/SOAP | Java/阿帕奇轴 Java/Apache Axis |
乙 B | 异步 Asynchronous | 分配 Distribution | 相关ID Correlation ID | 消息队列 Message Queue | C#/微软MSMQ C#/Microsoft MSMQ |
C C | 异步 Asynchronous | 拍卖 Auction | 相关ID Correlation ID | 发布-订阅 Publish-Subscribe | TIBCO 活跃企业 TIBCO Active-Enterprise |
第一个实现使用用 Java 和 Apache Axis 实现的同步 Web 服务。与每个银行的通信通过单独的 HTTP 通道进行,该通道既充当请求通道又充当回复通道。因此,聚合策略基于各个回复通道,不需要关联。第二种实现使用带有消息队列的异步方法。我们使用 Microsoft 的 MSMQ 来实现它,但使用 JMS 或 IBM WebSphere MQ 的实现可能看起来非常相似。最后一个实现使用拍卖方法并利用 TIBCO 的发布-订阅基础设施和实现流程管理器的TIB/IntegrationManager 工具图案。在选项 B 和 C 中,所有回复消息都到达单个通道,并且实现使用相关标识符将回复消息与客户贷款报价查询相关联。
The first implementation uses synchronous Web services implemented in Java and Apache Axis. The communication with each bank occurs over a separate HTTP channel, which serves as both a request and reply channel. Therefore, the aggregation strategy is based on individual reply channels and does not require correlation. The second implementation uses an asynchronous approach with message queues. We implement it using Microsoft's MSMQ, but an implementation using JMS or IBM WebSphere MQ could look very similar. The last implementation uses an Auction approach and leverages TIBCO's publish-subscribe infrastructure and the TIB/IntegrationManager tool that implements the Process Manager pattern. In option B and C, all reply messages arrive on a single channel and the implementations use Correlation Identifiers to associate reply messages to customer loan quote inquiries.
作者:康拉德·F·德克鲁兹
by Conrad F. D'Cruz
本节描述使用 Java 和 XML Web 服务实现贷款经纪人示例。我们使用开源 Apache Axis 工具包来为我们处理 Web 服务机制。我们不希望这成为 Java 开发中的一个练习,因此我们选择这个工具集来抽象实现同步 Web 服务接口的复杂性。相反,本节的讨论重点是我们在设计同步消息传递解决方案时做出的设计决策。
This section describes the implementation of the loan broker example using Java and XML Web services. We use the open source Apache Axis toolkit to take care of the Web services mechanics for us. We do not want this to be an exercise in Java development, so we chose this tool set to abstract the complexities of implementing a synchronous Web service interface. Instead, the discussion in this section focuses on the design decisions that we make in designing a synchronous messaging solution.
Web 服务解决方案架构
Web Services Solution Architecture
该图显示了贷款经纪人示例的同步预测 Web 服务实现的总体体系结构。贷款经纪人和解决方案的其余部分之间有七个重要的接口。如前所述,SOAP over HTTP 用于在每对参与者之间进行通信。
This figure shows the overall architecture of the synchronous predictive Web service implementation of the loan broker example. There are seven significant interfaces between the loan broker and the rest of the solution. As indicated, SOAP over HTTP is used to communicate between each pair of participants.
第一个接口是贷款经纪人的入口点,客户端应用程序使用它来传递包含贷款申请信息的消息。客户端通过相同的接口接收从贷款经纪人返回的查询结果。尽管 Axis 服务器未在此图中显示,但它接收来自客户端的消息并实现Service Activator 。
The first interface is the entry point into the loan broker that the client application uses to pass in the message containing the loan application information. The client receives the results of the query back from the loan broker via the same interface. Although the Axis server is not shown in this diagram, it receives the Message from the client and implements the Service Activator.
第二个接口是贷款经纪人和信贷机构之间的接口。信贷机构是一个外部机构,为贷款经纪人提供 Web 服务接口,以获取银行所需的其他客户数据。贷款经纪人使用来自信贷机构的数据来丰富传入的请求,从而实现内容丰富器。
The second interface is between the loan broker and the credit agency. The credit agency is an external bureau and provides a Web service interface for the loan broker to acquire additional customer data that is required by the banks. The loan broker enriches the incoming request with the data from the credit agency, implementing a Content Enricher.
接下来的五个接口位于贷款经纪人和五家银行之间。每个接口都用于获取该特定银行的贷款利率报价。每个银行接口都提供相同的功能(获取报价),但可以为关联的 SOAP 消息指定不同的格式。因此,贷款经纪人在询问银行之前必须将报价请求消息翻译成每个特定银行所要求的格式。
The next five interfaces are between the loan broker and each of the five banks. Each interface is used to obtain the rate quote for a loan from that particular bank. Each bank interface provides the same functionality (obtain a quote) but may specify a different format for the associated SOAP messages. Therefore, the loan broker has to translate the quote request message to the format required by each particular bank before querying the bank.
贷款经纪人使用预测路由直接联系每家银行;也就是说,参与报价收集的银行集合在被要求报价之前就已经知道。在此阶段,贷款经纪人应用程序使用接收者列表模式将请求发送到选定的银行。由于每家银行可以对请求使用不同的格式,因此贷款经纪人需要使用一组消息转换器将请求转换为每家银行所需的格式。
The loan broker contacts each bank directly using predictive routing; that is, the set of banks that participate in the quote gathering is known right before they are called for a quote. In this stage the loan broker application sends the request to the selected banks using the Recipient List pattern. Because each bank can use a different format for the request, the loan broker needs to use an array of Message Translators to convert the request into the format required by each bank.
同样,每条回复消息都必须使用Normalizer 转换为通用格式,以便所有回复都可以聚合到单个最佳报价中。请求消息和响应消息同步传递;也就是说,客户和贷款经纪人将被阻止,直到每家银行做出响应或超时。贷款经纪人将所有回复汇总为单个最佳报价,并将最佳报价回复发送回客户。
Likewise, each reply message has to be converted to a common format using a Normalizer , so that all replies can be aggregated into the single best quote. The request and response messages are passed synchronously; that is, the client and loan broker are blocked until each bank responds or times out. The loan broker aggregates all responses into the single best quote and sends the best quote reply back to the client.
同步消息传递在某些需要简单解决方案的问题领域非常有用。通过使用同步消息传递,我们不必担心处理异步事件、线程安全或支持它们所需的基础设施。客户端调用贷款经纪人 Web 服务,然后等待服务器的响应。该解决方案中有多个组件,每个组件都会同步调用下一个组件并等待响应。
Synchronous messaging is useful in some problem domains where a simple solution is necessitated. By using synchronous messaging, we do not have to worry about handling asynchronous events, thread safety, or the infrastructure needed to support them. The client invokes the loan broker Web service and then waits for the response from the server. There are several components in this solution, and each component makes a synchronous call to the next component and waits for the response.
XML Web 服务依赖于简单对象访问协议 (SOAP)。SOAP 规范已提交给 W3C,并定义了一个基于 XML 的协议,用于在分散式和分布式系统之间交换消息。有关 SOAP 的更多详细信息,请参阅万维网联盟网站( www.w3.org/TR/SOAP )上的文档。
XML Web services rely on the Simple Object Access Protocol (SOAP). The SOAP specification was submitted to the W3C and defines an XML-based protocol for the purpose of exchanging messages between decentralized and distributed systems. For more details on SOAP, please refer to the documents at the World Wide Web Consortium Web site (www.w3.org/TR/SOAP).
不幸的是, SOAP 中的S不再有效。我们开玩笑地假设 SOAP 被重命名为“复杂远程访问协议”(您可以找出缩写词)。说真的,设计一个健壮的 Web 服务接口需要我们深入研究许多术语和相关的设计权衡。虽然本书不是对 Web 服务的介绍,但我们认为简要讨论以下设计注意事项很重要:
Unfortunately, the S in SOAP is no longer valid. We have jokingly postulated that SOAP be renamed to Complex Remote Access Protocolyou figure out the acronym. Seriously, designing a robust Web services interface requires us to dive into a number of terminologies and associated design trade-offs. While this book is not an introduction to Web services, we feel it is important to briefly discuss the following design considerations:
传输协议
Transport protocol
异步消息传递与同步消息传递
Asynchronous messaging versus synchronous messaging
编码风格(SOAP 编码与文档/文字)
Encoding style (SOAP encoding vs. doc/literal)
绑定样式(RPC 与文档样式)
Binding style (RPC vs. document-style)
可靠性和安全性
Reliability and security
创建 SOAP 规范的目的是允许应用程序以技术中立的方式通过网络对服务进行同步 RPC 式调用。SOAP 规范实际上为 Web 服务的每次调用定义了单独的请求和响应消息(如描述服务的 WSDL 文档中所定义)。尽管 SOAP 是在开发时考虑到消息传递的,但绝大多数 Web 服务应用程序都使用 HTTP 作为传输协议。HTTP 是一个自然的选择,因为它是 Web 上最常用的协议,并且可以穿越防火墙。然而,HTTP 是超文本传输协议,它的设计目的是允许使用 Web 浏览器的用户通过 Internet 检索文档,而不是让应用程序相互通信。HTTP 本质上是不可靠的,并且是为同步文档检索而设计的,客户端应用程序使用相同的连接将请求发送到服务器并接收响应。因此,使用 HTTP 的 Web 服务将始终使用同步请求/响应消息传递,因为通过不可靠的通道进行异步消息传递就像将瓶子扔进大海一样有用。
The SOAP specification was created to allow applications to make synchronous RPC-style calls to a service across the network in a technology-neutral way. The SOAP specification actually defines a separate request and response message for each invocation to a Web service (as defined in the WSDL document describing the service). Even though SOAP was developed with messaging in mind, the vast majority of the Web service applications use HTTP as the transport protocol. HTTP is a natural choice because it is the most commonly used protocol on the Web and can sneak through firewalls. However, HTTP is the HyperText Transfer Protocolit was designed to allow users with Web browsers to retrieve documents over the Internet, not for applications to communicate with each other. HTTP is inherently unreliable and designed for synchronous document retrievalthe client application uses the same connection for sending the request to the server and receiving the response. Therefore, Web services that use HTTP will invariably use synchronous request/response messaging because asynchronous messaging over an unreliable channel is about as useful as dropping a bottle into the ocean.
在 Web 服务的同步实现中,从请求提交到服务器起,客户端连接就保持打开状态。客户端将等待,直到服务器发回响应消息。使用同步RPC通信的优点是客户端应用程序可以在很短的时间内知道Web服务操作的状态(要么收到响应,要么超时)。使用同步消息传递的一个严重限制是服务器可能必须处理大量并发连接,因为每个并发客户端在等待结果时都维护一个打开的连接。这导致服务器应用程序变得越来越复杂。如果对同步服务提供者的调用之一失败,
In a synchronous implementation of a Web service, the client connection remains open from the time the request is submitted to the server. The client will wait until the server sends back the response message. The advantage of using the synchronous RPC communication is that the client application knows the status of the Web service operation in a very short time (either it receives a response or it times out). A serious limitation of using synchronous messaging is that the server may have to deal with a large number of concurrent connections because each concurrent client maintains an open connection while waiting for the results. This causes the server application to become increasingly complex. If one of the invocations to a synchronous service provider fails, the server application has to provide the mechanism to trap the failure and recover and reroute the processing or flag an error before continuing with the other synchronous invocations.
目前,大多数 Web 服务工具包默认仅支持同步消息传递。然而,一些供应商使用现有的标准和工具(例如异步消息队列框架)模拟了 Web 服务的异步消息传递。一些组织、公司和 Web 服务工作组已经认识到对异步消息传递支持的需求,并正在着手定义标准(例如 WS-ReliableMessaging)。有关 Web 服务标准的最新信息,请参阅万维网联盟网站 http://www.w3.org/并参阅第 14 章“结束语”。
At the present time, most Web services toolkits support only synchronous messaging by default. However, using existing standards and tools such as asynchronous message queuing frameworks, some vendors have emulated asynchronous messaging for Web services. Several organizations, companies, and the Web services working groups have recognized the need for asynchronous messaging support and are moving toward defining standards (e.g., WS-ReliableMessaging). For the latest on Web services standards, please refer to the World Wide Web Consortium Web site at http://www.w3.org/ and see Chapter 14, "Concluding Remarks."
SOAP 编码的概念引起了相当多的争论和混乱。SOAP 规范定义了一种称为编码样式的模式,由encodingStyle属性指定。此模式可以采用两个值:encoded(通过将属性值设置为http://schemas.xmlsoap.org/soap/encoding/ )和literal(通过指定不同的[或没有]属性值)。此模式确定应用程序对象和参数如何在 XML 中“在线”表示。编码(也称为 SOAP 编码)是指 SOAP 规范的第 5 节,它定义了将编程语言类型映射到 XML 的原始机制。Literal(也称为doc/literal )意味着不要这样做。相反,类型信息由外部机制提供,很可能是 WSDL(Web 服务描述语言)文档,该文档使用 XML 模式来准确定义 SOAP 消息中使用的类型。
The notion of SOAP encoding has led to a fair amount of debate and confusion. The SOAP specification defines a mode called encoding style specified by the encodingStyle attribute. This mode can take on two values: encoded (by setting the attribute value to http://schemas.xmlsoap.org/soap/encoding/) and literal (by specifying a different [or no] attribute value). This mode determines how application objects and parameters are represented in the XML "on the wire." Encoded (also referred to as SOAP encoding) refers to Section 5 of the SOAP specification, which defines a primitive mechanism for mapping programming language types to XML. Literal (also called doc/literal) means don't do that. Instead, the type information is provided by an external mechanism, more likely than not a WSDL (Web Services Description Language) document that uses XML schema to define exactly what types are used in the SOAP message.
这是因为 SOAP 规范是在采用 W3C XML 架构定义 (XSD) 规范之前编写的。因此,最初的 SOAP 规范必须提供一种对类型信息以及为方法调用发送的参数进行编码的方法,因为没有公认的指定方法。真正发挥作用的是复杂的数据类型,例如数组。SOAP 规范的第 5.4.2 节定义了一种用于在 XML 中表示编程语言数组的特定机制,该机制使用特殊的SOAPEnc:Array模式类型。
This came about because the SOAP specification was written prior to the adoption of the W3C XML Schema Definition (XSD) specification. Thus, the original SOAP specification had to provide a way of encoding type information along with the parameters being sent for method calls because there was no accepted way of specifying it. Where this really comes into play is with complex data types such as Arrays. Section 5.4.2 of the SOAP specification defines a particular mechanism for representing programming language arrays in XML that uses a special SOAPEnc:Array schema type.
然而,自从采用 XML 模式(参见http://www.w3.org/TR/xmlschema-0/)以来,大多数语言都通过从 XML 指定它们自己的映射(或序列化规则)来不再需要 SOAP 编码编程语言类型的模式。例如,JAX-RPC 规范唯一地指定了 Java 类型如何映射到 XML 模式元素,反之亦然。这样就无需在 XML 中添加额外的编码信息。因此,SOAP 编码不再受到青睐,并已被文本编码所取代,该编码具有由 XML 模式文档(通常采用 WSDL 文档的形式)在外部指定的映射。
However, since the adoption of XML schema (see http://www.w3.org/TR/xmlschema-0/), most languages have rendered the need for SOAP encoding obsolete by specifying their own mappings (or serialization rules) from XML schema to the programming language types. For instance, the JAX-RPC specification uniquely specifies how Java types are mapped to XML schema elements, and vice versa. This obviates the need for extra encoding information in the XML. As a result, SOAP encoding is no longer favored and has been superceded by literal encoding with the mapping specified externally by an XML schema document, usually in the form of a WSDL document.
WSDL 规范在其 SOAP 绑定中指定了两种不同的绑定样式。绑定样式属性的值为RPC和Document 。这意味着,如果 WSDL 文档指定了一个将绑定样式属性设置为RPC 的操作,那么接收方必须使用 SOAP 规范第 7 节中的规则来解释该消息。例如,这意味着 SOAP 主体内的 XML 元素(称为包装元素)的名称必须与要调用的相应编程语言操作的名称相同,该元素中的每个消息部分必须与该编程语言操作的参数完全对应(在名称和顺序上),并且必须只返回一个元素(必须命名为XXXResponse ,其中XXX是语言中相应操作的名称),其中只包含一个元素,即操作的返回值。
The WSDL specification specifies two different binding styles in its SOAP binding. The values of the binding style attribute are RPC and Document. This means that if a WSDL document specifies an operation that has a binding style attribute set to RPC, then the receiver must interpret that message using the rules found in Section 7 of the SOAP specification. This means, for instance, that the XML element inside the SOAP body (called a wrapper element) must have a name identical to the name of the corresponding programming-language operation that is to be invoked, that each message part within that element must correspond exactly (in name and order) to a parameter of that programming-language operation, and that there must be only a single element returned (which must be named XXXResponse, where XXX is the name of the corresponding operation in the language) that contains inside it exactly one element, which is the return value of the operation.
文档装订风格要宽松得多。文档绑定样式中的消息必须仅由格式良好的 XML 组成。接收它并如何解释它取决于 SOAP 引擎。尽管如此,许多工具(例如来自 Microsoft 的工具)通常使用文档绑定样式和文字编码来表示 RPC 语义。即使使用文档样式,发送的消息也代表命令消息,其中要调用的操作和要传递的参数在文档中编码。
The Document binding style is much looser. A message in the Document binding style must simply be made up of well-formed XML. It is up to the SOAP engine that receives it how it will be interpreted. Having said that, many tools (such as those from Microsoft) commonly use Document binding style and Literal encoding to represent RPC semantics. Even though a Document style is used, the message being sent represents a Command Message , with the operation to be invoked and the parameters to be passed encoded in the Document.
在第 14 章“结束语”中,Sean Neville 描述了旨在解决 Web 服务的可靠性和安全性问题的不断发展的标准。
In Chapter 14, "Concluding Remarks," Sean Neville describes evolving standards that aim to address the issues of reliability and security for Web services.
我们的解决方案使用这些设计选择的最基本且可能仍然最流行的组合。贷款经纪人实现使用 SOAP over HTTP 进行同步通信,使用默认 SOAP 编码样式和 RPC 绑定对消息进行编码。这使得 Web 服务的行为非常类似于远程过程调用。我们走这条路是为了避免陷入对 Web 服务内部争论的困境(嗯,不会比我们已经做的更多),而是可以专注于将同步 Web 服务实现与其他实现进行对比。
Our solution uses the most basic and probably still most prevalent combination of these design choices. The loan broker implementation uses SOAP over HTTP with synchronous communication, encoding messages with the default SOAP encoding style and RPC binding. This makes the Web service behave very much like a Remote Procedure Invocation. We went this route so that we do not get stuck debating Web services internals (well, not any more than we already did) but can focus on contrasting the synchronous Web services implementation with the other implementations.
本节提供了 Axis 架构的简要描述,以帮助阐明我们设计中的要点。有关 Axis 的更多详细信息,请参阅 Apache Axis 网站:http://ws.apache.org/axis。
This section provides a brief description of the Axis architecture to help elucidate the significant points in our design. For additional details on Axis, please refer to the Apache Axis Web site at http://ws.apache.org/axis.
第 3 章“消息系统”将消息端点定义为应用程序用于连接消息通道以发送和接收消息的机制。在我们的贷款经纪人应用程序中,Axis 框架本身代表消息通道,其主要功能是代表用户应用程序处理消息。
Chapter 3, "Messaging Systems," defines a Message Endpoint as the mechanism an application uses to connect to a messaging channel in order to send and receive messages. In our loan broker application, the Axis framework itself represents the message channel, and its main function is to handle the processing of messages on behalf of the user application.
Axis 服务器实现服务激活器模式。第 10 章“消息传送端点”描述了服务激活器如何将消息通道连接到应用程序中的同步服务,以便在接收到消息时调用该服务。
The Axis server implements the Service Activator pattern. Chapter 10, "Messaging Endpoints," describes how a Service Activator connects a Message Channel to a synchronous service in an application so that when a message is received, the service is invoked.
服务激活器在 Axis 服务器内实现,因此开发人员无需费心处理此功能。因此,应用程序代码仅包含应用程序的业务逻辑,Axis 服务器负责所有消息处理服务。
The Service Activator is implemented within the Axis server so that the developer does not need to bother with this functionality. Therefore, the application code contains only business logic for the application, and the Axis server takes care of all message-handling services.
Axis 的客户端编程模型为客户端应用程序提供组件来调用端点 URL,然后接收来自服务器的响应消息。此应用程序中的贷款经纪人客户端是使用 Axis 客户端编程模型的同步客户端。在服务器内,有一个针对服务器支持的每种传输协议的侦听器。当客户端向端点发送消息时,Axis 框架内的传输侦听器会创建一个消息上下文对象,并将其通过框架中的请求链传递。消息上下文包含从客户端接收的实际消息以及传输客户端添加的关联属性。
The client-programming model of Axis provides the components for the client application to invoke an endpoint URL and then receive the response message from the server. The loan broker client in this application is a synchronous client that uses this client-programming model of Axis. Within the server there is a listener for each transport protocol supported by the server. When the client sends a message to the endpoint, a transport listener within the Axis framework creates a message context object and passes it through a request chain in a framework. The message context contains the actual message received from the client along with associated properties added by the transport client.
Axis 框架由一系列处理程序组成,这些处理程序根据部署配置以及调用框架的是客户端还是服务器以特定顺序调用。处理程序是消息流子系统的一部分,组合在一起称为链。请求消息由链中的一系列请求处理程序处理。任何响应消息都会通过一系列响应处理程序发送回相应的响应链。
The Axis framework is made up of a series of Handlers, which are invoked in a particular order depending on the deployment configuration and whether the client or server is invoking the framework. Handlers are part of the Message Flow subsystem and are grouped together and called Chains. Request messages are processed by a sequence of request Handlers in a Chain. Any response messages are sent back on the corresponding response Chain through a sequence of response Handlers.
上图显示了 Axis 框架内部的高级表示。有关 Axis 架构的详细讨论可以在http://ws.apache.org/axis中找到。
The figure above shows a high-level representation of the internals of the Axis framework. A detailed discussion on the architecture of Axis can be found at http://ws.apache.org/axis.
Axis 由多个子系统组成,这些子系统协同工作以提供消息通道的功能。 该框架可供客户端和服务器应用程序使用。
Axis consists of several subsystems that work together to provide the functionality of a Message Channel. This framework is available for use by both the client and server applications.
我们的示例的相关 Axis 子系统如下:
The relevant Axis subsystems for our example are as follows:
消息模型子系统定义 SOAP 消息的 XML 语法。
Message Model subsystem defines the XML syntax of the SOAP messages.
消息流子系统定义了处理程序和链来传递消息。
Message Flow subsystem defines Handlers and Chains to pass the messages.
服务子系统定义服务处理程序(SOAP、XML-RPC)。
Service subsystem defines the service handler (SOAP, XML-RPC).
传输子系统提供消息传输的替代方案(例如HTTP、JMS、SMTP)。
Transport subsystem provides alternatives for the transport of messages (e.g., HTTP, JMS, SMTP).
提供者子系统定义了不同类型的类(例如,java:RPC、EJB、MDB)的提供者。
Provider subsystem defines the providers for different types of classes (e.g., java:RPC, EJB, MDB).
如前所述,开发人员只需专注于创建实现业务逻辑的应用程序,然后即可将其部署在 Axis 服务器中。可通过三种技术将 Java 类部署为 Web 服务并使其可用作端点服务:我们将它们命名如下:
As mentioned earlier, the developer only needs to focus on creating an application that implements the business logic, which can then be deployed in the Axis server. There are three techniques for deploying a Java class as a Web service and making it available as an endpoint service; we have named them as follows:
自动部署
Automatic deployment
使用 Web 服务部署描述符
Using a Web Services Deployment Descriptor
从现有 WSDL 文档生成代理
Generate proxies from an existing WSDL document
第一种也是最简单的方法是将包含业务逻辑的类编写为 Java Web 服务 (JWS) 文件(这是扩展名为*.jws的 Java 源文件)。此类的方法包含业务逻辑。JWS文件不需要编译,只需将源文件复制到服务器上的webapps目录即可立即部署。每个公共方法现在都可以作为 Web 服务进行访问。此 JWS 文件的名称构成 Axis 服务器作为 Web 服务公开的端点的一部分,如以下 URL 所示:
The first and simplest way is to write the class containing the business logic as a Java Web Service (JWS) file (this is a Java source file with a *.jws extension). The methods of this class contain the business logic. The JWS file does not need to be compiled and can be instantly deployed by copying the source file into the webapps directory on the server. Each public method is now accessible as a Web service. The name of this JWS file forms part of the endpoint that the Axis server exposes as a Web service, as shown in the following URL:
http://主机名:端口号/axis/LoanBroker.jws
http://hostname:portnumber/axis/LoanBroker.jws
Axis 1.1 从服务器内部署的服务自动生成 WSDL 文档。WSDL 是一种 XML 格式,它描述 Web 服务的公共接口(即通过该接口可用的方法)以及服务的位置(即 URL)。WSDL 的自动生成允许其他应用程序检查 Web 服务类提供的远程接口。它还可用于自动生成客户端存根类,将 Web 服务调用封装在常规 Java 类中。这种方法的缺点是开发者无法控制部署参数。
Axis 1.1 automatically generates the WSDL document from services deployed within the server. WSDL is an XML format that describes the public interface of a Web service (i.e., the methods available through the interface) as well as the location of the service (i.e., the URL). The automatic generation of WSDL allows other applications to inspect the remote interface provided by the Web service class. It can also be used to automatically generate client stub classes that encapsulate the Web services call inside a regular Java class. The disadvantage of this method is that the developer cannot control the deployment parameters.
第二种技术使用 WSDD(Web 服务部署描述符)部署已编译的类,这允许开发人员控制部署参数,例如类范围。默认情况下,类会部署在请求范围内;也就是说,为收到的每个请求实例化该类的一个新实例。一旦处理完成,实例就会被销毁。如果该类必须在整个会话中持续存在,以便在一段时间内为同一客户端的多个请求提供服务,我们需要在会话范围内定义该类。对于某些应用程序,我们需要所有客户端都访问一个单例类;也就是说,该类必须被实例化并在应用程序处于活动状态的整个持续时间内可用,因此 Web 服务是在应用程序范围内定义的。
The second technique deploys a compiled class using a WSDD (Web Services Deployment Descriptor), which allows the developer to control the deployment parameters, such as the class scope. By default, a class gets deployed in the request scope; that is, a new instance of the class is instantiated for each request that is received. Once the processing is finished, the instance is destroyed. If the class has to persist for the entire session to service multiple requests from the same client over a period of time, we need to define the class in the session scope. For some applications, we need all clients to access a singleton class; that is, the class has to be instantiated and made available for the entire duration the application is active, and so the Web service is defined in the application scope.
最后一种技术比前两种技术更复杂,但允许从现有 WSDL 文档生成代理和框架(使用wsdl2java工具)。代理和骨架封装了所有与 SOAP 相关的代码,以便开发人员不必编写任何与 SOAP 相关(或与 Axis 相关)的代码,而是可以将业务逻辑直接插入到生成的骨架的方法体中。
The last technique is more complicated than the previous two techniques but allows the generation of proxies and skeletons from an existing WSDL document (using the wsdl2java tool). The proxies and skeletons encapsulate all SOAP-related code so that the developer does not have to write any SOAP-related (or Axis-related) code, but can instead insert the business logic right into the method bodies of the generated skeleton.
我们选择使用自动部署技术和 JWS 文件来实现所有 Web 服务,以便使设计和部署要求尽可能简单。在客户端,手动编写对 Web 服务的调用要容易得多。我们可以使用wsdl2java工具来生成存根,然后由客户端代码调用;然而,我们试图最大程度地减少代码生成量,因为它会使示例解决方案变得困难。
We chose to implement all our Web services using the Automatic Deployment technique, using JWS files, in order to keep the design and deployment requirements as simple as possible. On the client side, it is a lot easier to hand-code the call to the Web service. We could have used the wsdl2java tool to generate the stubs, which would then be called by the client code; however we tried to minimize the amount of code generation because it can make it difficult to walk through an example solution.
在继续讨论贷款经纪人应用程序之前,我们需要描述在实施解决方案时遵循的一些常规步骤,以帮助创建易于部署的应用程序。为了调用部署在服务器上的 Web 服务,任何客户端应用程序都需要知道端点 URL。在 Web 服务模型中,应用程序从公共服务注册表中查找它想要访问的 Web 服务的位置。
Before we proceed with a discussion of the loan broker application, we need to describe a few general steps that we follow when implementing the solution to help create an application that is easy to deploy. In order to invoke a Web service that is deployed on a server, any client application needs to know the endpoint URL. In the Web services model, an application looks up the location of a Web service it would like to access from a common service registry.
UDDI(通用描述、发现和集成)是此类存储库的标准。对 UDDI 的讨论超出了本节的范围;您可以在http://www.uddi.org获取更多信息。在我们的示例中,我们在应用程序本身中对端点 URL 进行了硬编码。但是,为了轻松部署示例代码,我们为服务器和客户端应用程序创建属性文件。属性文件包含主机名和端口号参数的名称-值对,它们与您的 Axis 服务器安装参数相匹配。这为您在环境中部署贷款经纪人应用程序提供了一定的灵活性。我们提供了一个实用方法,readProps(),在一些 Java 类中。此方法用于读取包含 Axis 服务器部署参数的文件。贷款经纪人应用程序的任何功能方面都不使用readProps ()方法。
UDDI (Universal Description, Discovery, and Integration) is a standard for such a repository. A discussion of UDDI is beyond the scope of this section; you can get additional information at http://www.uddi.org. In our example, we have hard-coded the endpoint URLs in the application itself. However, to make it easy to deploy the example code, we create properties files for both the server and client applications. The properties file has name-value pairs for the hostname and port number parameters, which match the parameters of your Axis server installation. This gives you some flexibility in deploying the loan broker application in your environment. We provide a utility method, readProps(), in some of the Java classes. This method is for reading files that contain the deployment parameters of the Axis server. The readProps() method is not used by any of the functional aspects of the loan broker application.
在任何分布式计算框架中,无论是Java RMI、CORBA还是SOAP Web服务,我们都需要将远程对象上的方法调用的参数定义为原始数据类型或可以序列化到网络和从网络传出的对象。这些参数是从客户端发送到服务器的消息对象的属性。为了使贷款经纪人解决方案保持简单,我们使用 Java String对象将贷款经纪人的响应返回给客户端。如果调用参数是原始数据类型(例如int、double等),则必须将它们包装在称为类型包装器的预定义对象包装器中(例如Integer 、Double等)。
In any distributed computing framework, whether it is Java RMI, CORBA, or SOAP Web services, we need to define parameters of the method calls on remote objects as primitive data types or objects that can be serialized to and from the network. These parameters are the properties of the message objects that are sent from the client to the server. To keep the loan broker solution simple, we use a Java String object to return the response from the loan broker to the client. If the calling parameters are primitive data types (e.g., int, double, etc.), they have to be wrapped in the predefined object wrappers called the type wrappers (e.g., Integer, Double, etc.).
下页上的图显示了贷款经纪人组件的类图。贷款经纪人的核心业务逻辑封装在LoanBrokerWS类中。此类继承自 Axis 框架提供的类,该框架实现Service Activator,以便在 SOAP 请求到达时调用LoanBrokerWS 类中的方法。LoanBrokerWS引用一组网关类,这些网关类实现与外部实体(例如信贷机构和银行)接口的详细信息。此逻辑封装在CreditAgencyGateway 、 LenderGateway和BankQuoteGateway类中。
The figure on the following page shows the class diagram for the loan broker component. The core business logic for the loan broker is encapsulated inside the LoanBrokerWS class. This class inherits from a class provided by the Axis framework that implements a Service Activator to invoke a method in the LoanBrokerWS class when a SOAP request arrives. The LoanBrokerWS references a set of gateway classes that implement details of interfacing with external entities, such as the credit agency and the banks. This logic is encapsulated inside the classes CreditAgencyGateway, LenderGateway, and BankQuoteGateway.
贷款经纪人类图
Loan Broker Class Diagram
贷款经纪人与外界的唯一服务接口是客户端访问贷款经纪人服务的消息端点。无需定义消息传递网关,因为 Axis 框架代表应用程序充当网关。
The only service interface from the loan broker to the outside world is the message endpoint for the client to access the loan broker service. There is no need to define a Messaging Gateway , since the Axis framework acts as the gateway on behalf of the application.
以下序列图说明了同步预测 Web 服务示例的组件之间的交互。贷款经纪人首先调用信用机构网关组件,该组件丰富了提供的最低数据,包括客户的信用评分和信用历史长度。贷款经纪人使用丰富的数据来调用贷方网关。该组件实现了接收者列表,该列表使用提供的所有数据来选择可以为贷款请求提供服务的贷方集。
The following sequence diagram illustrates the interaction between the components for the synchronous predictive Web service example. The loan broker first calls the credit agency gateway component, which enriches the minimum data provided with a credit score and the length of credit history for the customer. The loan broker uses the enriched data to call the lender gateway. This component implements the Recipient List that uses all the data provided to choose the set of lenders that can service the loan request.
然后贷款经纪人致电银行报价网关。该组件通过依次调用每个组来执行预测路由操作。银行组件对现实银行业务的接口进行建模。例如,Bank1类是Bank1WS.jws Web 服务的接口,该服务对银行操作进行建模。当收到贷款请求时,在根据所有参数生成利率报价之前,需要执行一些文书工作。在此示例中,报价由“虚拟”银行 Web 服务生成。
The loan broker then calls the bank quote gateway. This component performs the predictive routing operation by calling each bank in turn. The bank components model the interface to a real-world banking operation. For example, the Bank1 class is the interface to the Bank1WS.jws Web service that models the banking operation. When a loan request comes in, there is some amount of clerical work performed before generating the rate quote based on all the parameters. In this example, the rate quote is generated by a "dummy" banking Web service.
银行报价网关汇总来自银行的响应,并从收到的所有报价中选择最佳报价。响应被发送回贷款经纪人,贷款经纪人根据最佳报价格式化数据并将报告返回给客户。
The bank quote gateway aggregates the responses from the banks and chooses the best quote from all the quotes received. The response is sent back to the loan broker, which formats the data from the best quote and returns the report to the client.
贷款经纪人序列图
Loan Broker Sequence Diagram
为了使数字易于管理,我们在序列图中仅显示了五个银行中的两个。根据为特定请求生成的接收者列表,贷款经纪人可以联系一家银行或所有五家银行来为客户请求提供服务。
We have shown only two of the five banks in the sequence diagram in the interest of keeping the figure manageable. Based on the Recipient List generated for a particular request, the loan broker could contact exactly one bank or all five banks to service the client request.
该图突出显示了对每家银行的报价请求的顺序处理。顺序处理有一些优点,因为我们可以在单个线程中运行应用程序,依次调用每个存储体。应用程序不需要生成线程来请求每个银行的报价,因此我们不必担心并发问题。然而,获得响应报价的总时间很长,因为贷款经纪人必须等待每家银行的响应,然后才能向贷方列表中的下一家银行提交请求。最终的结果是客户提交请求后需要等待很长时间才能得到结果报价。
The diagram highlights the sequential processing of the quote requests to each bank. The sequential processing has some advantages because we can run the application in a single thread, calling each bank in turn. The application does not need to spawn threads to request a quote from each bank, and so we don't have to worry about concurrency issues. However, the total time to get the response quote is high, since the loan broker has to wait for a response from each bank before submitting a request to the next bank in the lenders list. The net result is that the customer will have to wait a long time to get the result quote back after submitting the request.
我们现在开始设计贷款经纪人应用程序的功能方面。如前所述,Axis 框架可以支持客户端和服务器应用程序。这允许远程客户端通过网络访问已发布的端点。服务器应用程序可能还需要像客户端一样运行并访问某些其他 Web 服务端点,无论该端点位于同一服务器还是远程服务器上。我们通过将解决方案的关键组件建模为 Web 服务来演示这一点,尽管它们运行在服务器的同一实例上。
We now start designing the functional aspects of the loan broker application. As stated earlier, the Axis framework can support both the client and server application. This allows a remote client to access the published endpoint across the network. A server application may also need to act like a client and access some other Web service endpoint, regardless of whether that endpoint is on the same server or a remote server. We demonstrate this by modeling the key components of our solution as Web services, albeit running on the same instance of our server.
贷款经纪人解决方案必须实现以下功能:
The loan broker solution has to implement the following functions:
接受客户请求
Accept Client Requests
检索信用机构数据
Retrieve Credit Agency Data
实施信用代理服务
Implement the Credit Agency Service
获取报价
Obtain Quotes
实施银行业务
Implement the Banking Operations
贷款经纪人在名为LoanBrokerWS.jws的 JWS 文件中实现。它将单个公共方法公开为 Web 服务:GetLoanQuote 。贷款经纪人需要客户提供三项数据才能开始处理贷款请求:充当客户识别号的社会保障号 (SSN)、贷款金额以及贷款期限(以月为单位):
The loan broker is implemented in a JWS file named LoanBrokerWS.jws. It exposes a single public method as a Web service: GetLoanQuote. The loan broker needs three pieces of data from the client to start processing a loan request: the social security number (SSN) that acts as the customer identification number, the amount of the loan, and the duration of the loan in months:
公共字符串 getLoanQuote(int ssn,双倍贷款金额,int 贷款期限){
字符串结果=“”;
results = results + "客户ssn= " + ssn + " 请求贷款金额= " +
贷款金额 + " for " + 贷款期限 + " 月数" + "\n\n";
结果 = 结果 + this.getLoanQuotesWithScores(ssn,loanamount,loanduration);
返回结果;
}
public String getLoanQuote(int ssn, double loanamount, int loanduration) {
String results = "";
results = results + "Client with ssn= " + ssn + " requests a loan of amount= " +
loanamount + " for " + loanduration + " months" + "\n\n";
results = results + this.getLoanQuotesWithScores(ssn,loanamount,loanduration);
return results;
}
此方法中的唯一步骤是调用方法getLoanQuotesWithScores 。此方法返回一个字符串,该字符串沿着 Axis 框架中的响应链发送回客户端。在链的末端,接收请求的传输侦听器接受来自 Axis 框架内的响应消息,并将其发送回网络上的客户端。
The only step in this method is the call to the method getLoanQuotesWithScores. This method returns a string, which is sent back to the client along a response chain in the Axis framework. At the end of the chain, the transport listener that received the request accepts the response message from within the Axis framework and sends it back to the client on the network.
如前所述,由于我们对贷款经纪人组件使用 JWS 文件的自动部署,Axis 会为LoanBrokerWS服务生成 WSDL 文件。接下来显示LoanBrokerWS.jws文件的 WSDL 文件。
As described earlier, since we use the Automatic Deployment of the JWS file for the loan broker component, Axis generates the WSDL file for the LoanBrokerWS service. The WSDL file for the LoanBrokerWS.jws file is shown next.
<wsdl:定义 xmlns:wsdl =“http://schemas.xmlsoap.org/wsdl/”
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:message name="getLoanQuoteRequest">
<wsdl:part name="ssn" type="xsd:int"/>
<wsdl:part name="loanamount" type="xsd:double"/>
<wsdl:part name="loanduration" type="xsd:int"/>
</wsdl:消息>
<wsdl:message name="getLoanQuoteResponse">
<wsdl:part name="getLoanQuoteReturn" type="xsd:string"/>
</wsdl:消息>
<wsdl:portType name="LoanBrokerWS">
<wsdl:操作名称=“getLoanQuote”parameterOrder=“ssn贷款金额贷款持续时间”>
<wsdl:输入消息=“intf:getLoanQuoteRequest”
名称=“getLoanQuoteRequest”/>
<wsdl:输出消息=“intf:getLoanQuoteResponse”
名称=“getLoanQuoteResponse”/>
</wsdl:操作>
</wsdl:端口类型>
<wsdl:binding name="LoanBrokerWSSoapBinding" type="intf:LoanBrokerWS">
<wsdlsoap:绑定样式=“rpc”传输=“http://schemas.xmlsoap.org/soap/http”/>
<wsdl:操作名称=“getLoanQuote”>
<wsdlsoap:操作soapAction=""/>
<wsdl:input name="getLoanQuoteRequest">
<wsdlsoap:bodyencodingStyle =“http://schemas.xmlsoap.org/soap/encoding/”
命名空间=“...”使用=“编码”/>
</wsdl:输入>
<wsdl:output name="getLoanQuoteResponse">
<wsdlsoap:bodyencodingStyle =“http://schemas.xmlsoap.org/soap/encoding/”
命名空间=“...”使用=“编码”/>
</wsdl:输出>
</wsdl:操作>
</wsdl:绑定>
<wsdl:服务名称=“LoanBrokerWSService”>
<wsdl:端口绑定 =“intf:LoanBrokerWSSoapBinding”名称 =“LoanBrokerWS”>
<wsdlsoap:地址位置=“http://192.168.1.25:8080/axis/LoanBrokerWS.jws”/>
</wsdl:端口>
</wsdl:服务>
</wsdl:定义>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<wsdl:message name="getLoanQuoteRequest">
<wsdl:part name="ssn" type="xsd:int"/>
<wsdl:part name="loanamount" type="xsd:double"/>
<wsdl:part name="loanduration" type="xsd:int"/>
</wsdl:message>
<wsdl:message name="getLoanQuoteResponse">
<wsdl:part name="getLoanQuoteReturn" type="xsd:string"/>
</wsdl:message>
<wsdl:portType name="LoanBrokerWS">
<wsdl:operation name="getLoanQuote" parameterOrder="ssn loanamount loanduration">
<wsdl:input message="intf:getLoanQuoteRequest"
name="getLoanQuoteRequest"/>
<wsdl:output message="intf:getLoanQuoteResponse"
name="getLoanQuoteResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="LoanBrokerWSSoapBinding" type="intf:LoanBrokerWS">
<wsdlsoap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="getLoanQuote">
<wsdlsoap:operation soapAction=""/>
<wsdl:input name="getLoanQuoteRequest">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="..." use="encoded"/>
</wsdl:input>
<wsdl:output name="getLoanQuoteResponse">
<wsdlsoap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
namespace="..." use="encoded"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="LoanBrokerWSService">
<wsdl:port binding="intf:LoanBrokerWSSoapBinding" name="LoanBrokerWS">
<wsdlsoap:address location="http://192.168.1.25:8080/axis/LoanBrokerWS.jws"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
出于空间考虑,我们压缩了 WSDL 文档以突出显示最重要的元素。文档底部的<wsdl:service>元素定义服务名称 ( LoanBrokerWSService ) 和端点位置。<wsdl:operation>元素定义方法(操作)名称以及客户端访问LoanBrokerWS Web服务所需的参数。<wsdl:message>标签中定义的两条消息定义了getLoanQuote操作的请求和响应消息: getLoanQuoteRequest 和getLoanQuoteResponse使用它们传递到 Web 服务或从 Web 服务接收的适当参数。< wsdlsoap:binding>元素确认我们正在使用本章开头讨论的 RPC 绑定样式,而<wsdlsoap:body>表明我们正在使用 SOAP 编码(而不是doc/literal )。
In the interest of space, we condensed the WSDL document to highlight the most significant elements. The <wsdl:service> element at the bottom of the document defines the service name (LoanBrokerWSService) and the endpoint location. The <wsdl:operation> element defines the method (operation) name along with the parameters required for the client to access the LoanBrokerWS Web service. The two messages defined in the <wsdl:message> tags define the request and the response messages for the getLoanQuote operation: getLoanQuoteRequest and getLoanQuoteResponse with the appropriate parameters they pass in to or receive from the Web service. The <wsdlsoap:binding> element confirms that we are using the RPC binding style that we discussed at the beginning of this chapter, while the <wsdlsoap:body> reveals that we are using SOAP encoding (as opposed to doc/literal).
如果您想从服务器查看 WSDL 文件,可以在浏览器窗口中键入以下 URL,其中主机名和端口号替换为服务器安装的相应值:
If you want to see the WSDL file from your server, you can type in the following URL in your browser window where hostname and portnumber are replaced with the appropriate values of your server installation:
http://主机名:端口号/axis/LoanBrokerWS.jws?wsdl
http://hostname:portnumber/axis/LoanBrokerWS.jws?wsdl
贷款经纪人的另一个要求是收集客户的额外数据以完成贷款请求申请。LoanBrokerWS的下一阶段实现Content Enricher ,如下所示。
Another requirement for the loan broker is to gather additional data on the customer to complete the loan request application. The next stage of the LoanBrokerWS implements the Content Enricher , as shown below.
私有字符串 getLoanQuotesWithScores
(int de_ssn, double de_loanamount, int de_duration) {
字符串 qws_结果 =
“客户的附加数据:信用评分和信用记录长度\n”;
int ssn = de_ssn;
双倍贷款金额 = de_loanamount;
int 贷款期限 = de_duration;
int 信用分数 = 0;
int 信用历史长度 = 0;
CreditProfile 信用配置文件 = CreditAgencyGateway.getCustomerCreditProfile(ssn);
Credit_score = Creditprofile.getCreditScore();
Credit_history_length = Creditprofile.getCreditHistoryLength();
qws_results = qws_results + "信用评分= " + Credit_score +
" 信用记录长度= " + Credit_history_length;
qws_results = qws_results + "\n\n";
qws_results = qws_results + "所有回复银行的最佳报价详情为
如下所示:\n\n";
qws_results = qws_results + getResultsFromLoanClearingHouse
(ssn,贷款金额,贷款期限,信用历史长度,信用分数);
qws_results = qws_results + "\n\n";
返回 qws_结果;
}
private String getLoanQuotesWithScores
(int de_ssn, double de_loanamount, int de_duration) {
String qws_results =
"Additional data for customer: credit score and length of credit history\n";
int ssn = de_ssn;
double loanamount = de_loanamount;
int loanduration = de_duration;
int credit_score = 0;
int credit_history_length = 0;
CreditProfile creditprofile = CreditAgencyGateway.getCustomerCreditProfile(ssn);
credit_score = creditprofile.getCreditScore();
credit_history_length = creditprofile.getCreditHistoryLength();
qws_results = qws_results + "Credit Score= " + credit_score +
" Credit History Length= " + credit_history_length;
qws_results = qws_results + "\n\n";
qws_results = qws_results + "The details of the best quote from all banks that responded are
shown below: \n\n";
qws_results = qws_results + getResultsFromLoanClearingHouse
(ssn,loanamount,loanduration,credit_history_length,credit_score);
qws_results = qws_results + "\n\n";
return qws_results;
}
我们现在设计信贷代理业务,这是贷款经纪人应用程序的下一个逻辑领域。为了将 SOAP 代码排除在贷款经纪人应用程序之外,并最大限度地减少贷款经纪人和信贷机构之间的依赖关系,我们使用网关模式 [EAA],它有两个关键优点:首先,它抽象了贷款经纪人和信贷机构之间的技术细节。来自应用程序的通信。其次,如果我们选择将网关接口与网关实现分离,我们可以用Service Stub [ EAA ] 替换实际的外部服务进行测试。
We now design the credit agency operations, which is the next logical area of the loan broker application. In order to keep the SOAP code out of the loan broker application and minimize the dependencies between the loan broker and the credit agency, we use the Gateway pattern [EAA], which has two key advantages: First, it abstracts the technical details of the communication from the application. Second, if we choose to separate the gateway interface from the gateway implementation, we can replace the actual external service with a Service Stub [EAA] for testing.
我们的CreditAgencyGateway 抽象了CreditAgencyGateway 和信用机构Web服务 ( CreditAgencyWS ) 之间通信的技术细节。 如果需要,我们可以用测试存根替换 Web 服务。网关接收客户识别号 (SSN) 并从信贷机构获取其他数据。信贷机构返回的两项数据是客户的信用评分和信用记录长度,这两项数据都是完成贷款申请所必需的。该网关包含用于访问 CreditAgencyWS.jws 文件中实现的CreditAgencyWS 的所有客户端代码。
Our CreditAgencyGateway abstracts the technical details of the communication between the CreditAgencyGateway and the credit agency Web service (CreditAgencyWS). We could replace the Web service with a test stub if needed. The gateway takes in the customer identification number (SSN) and acquires additional data from the credit agency. The two pieces of data returned by the credit agency are the credit score and the length of credit history for the customer, both of which are needed to complete the loan application. This gateway contains all the client-side code to access the CreditAgencyWS implemented in the CreditAgencyWS.jws file.
公共静态 CreditProfile getCustomerCreditProfile(int ssn){
int 信用分数 = 0;
int 信用历史长度 = 0;
CreditProfile 信用档案 = null;
尝试{
CreditAgencyGateway.readProps();
信用档案=新的信用档案();
String Creditagency_ep = "http://" + 主机名 + ":" + 端口号 +
“/axis/CreditAgencyWS.jws”;
整数 i1 = new Integer(ssn);
服务service = new Service();
呼叫 call = (呼叫) service.createCall();
call.setTargetEndpointAddress( new java.net.URL(creditagency_ep) );
call.setOperationName("getCreditHistoryLength");
call.addParameter( "op1", XMLType.XSD_INT, ParameterMode.IN );
call.setReturnType( XMLType.XSD_INT );
Integer ret1 = (Integer) call.invoke( new Object [] {i1});
Credit_history_length = ret1.intValue();
call.setOperationName("getCreditScore");
Integer ret2 = (Integer) call.invoke( new Object [] {i1});
Credit_score = ret2.intValue();
信用档案.setCreditScore(credit_score);
Creditprofile.setCreditHistoryLength(credit_history_length);
Thread.sleep(credit_score);
}catch(异常前){
System.out.println("访问 CreditAgency Web 服务时出错");
}
返回信用档案;
}
}
public static CreditProfile getCustomerCreditProfile(int ssn){
int credit_score = 0;
int credit_history_length = 0;
CreditProfile creditprofile = null;
try{
CreditAgencyGateway.readProps();
creditprofile = new CreditProfile();
String creditagency_ep = "http://" + hostname + ":" + portnum +
"/axis/CreditAgencyWS.jws";
Integer i1 = new Integer(ssn);
Service service = new Service();
Call call = (Call) service.createCall();
call.setTargetEndpointAddress( new java.net.URL(creditagency_ep) );
call.setOperationName("getCreditHistoryLength");
call.addParameter( "op1", XMLType.XSD_INT, ParameterMode.IN );
call.setReturnType( XMLType.XSD_INT );
Integer ret1 = (Integer) call.invoke( new Object [] {i1});
credit_history_length = ret1.intValue();
call.setOperationName("getCreditScore");
Integer ret2 = (Integer) call.invoke( new Object [] {i1});
credit_score = ret2.intValue();
creditprofile.setCreditScore(credit_score);
creditprofile.setCreditHistoryLength(credit_history_length);
Thread.sleep(credit_score);
}catch(Exception ex){
System.out.println("Error accessing the CreditAgency Webservice");
}
return creditprofile;
}
}
使用此网关的目的是展示服务器应用程序如何使用 Axis 客户端框架来访问同一服务器或远程服务器上的另一个 Web 服务。
The purpose of using this Gateway is to show how a server application uses the Axis client framework to access another Web service either on the same server or on a remote server.
信用机构 Web 服务在CreditAgencyWS.jws中进行编码,如下所示。完成贷款申请所需的两个重要数据是客户信用评分和客户信用记录的长度。信用机构拥有每个具有信用记录的客户的数据,并且可以使用客户识别号进行访问。
The credit agency Web service is coded in CreditAgencyWS.jws, shown below. The two significant pieces of data needed to complete the loan application are the customer credit score and the length of the customer's credit history. The credit agency has this data for each customer with a credit history, and it can be accessed using the customer identification number.
public int getCreditScore(int de_ssn) 抛出异常
{
int 信用评分;
Credit_score = (int)(Math.random()*600+300);
返回信用分数;
}
public int getCreditHistoryLength(int de_ssn) 抛出异常
{
int 信用历史长度;
Credit_history_length = (int)(Math.random()*19+1);
返回信用历史长度;
}
public int getCreditScore(int de_ssn) throws Exception
{
int credit_score;
credit_score = (int)(Math.random()*600+300);
return credit_score;
}
public int getCreditHistoryLength(int de_ssn) throws Exception
{
int credit_history_length;
credit_history_length = (int)(Math.random()*19+1);
return credit_history_length;
}
以下代码显示CreditAgencyWS.jws文件的 WSDL 文件。该文件由 Axis 服务器自动生成。
The following code shows the WSDL file for the CreditAgencyWS.jws file. This file was automatically generated by the Axis server.
<wsdl:定义 xmlns:wsdl =“http://schemas.xmlsoap.org/wsdl/”
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
<wsdl:消息名称=“getCreditScoreResponse”>
<wsdl:part name="getCreditScoreReturn" type="xsd:int"/>
</wsdl:消息>
<wsdl:message name="getCreditHistoryLengthRequest">
<wsdl:part name="de_ssn" type="xsd:int"/>
</wsdl:消息>
<wsdl:message name="getCreditScoreRequest">
<wsdl:part name="de_ssn" type="xsd:int"/>
</wsdl:消息>
<wsdl:message name="getCreditHistoryLengthResponse">
<wsdl:part name="getCreditHistoryLengthReturn" type="xsd:int"/>
</wsdl:消息>
<wsdl:portType name="CreditAgencyWS">
<wsdl:操作名称=“getCreditHistoryLength”parameterOrder=“de_ssn”>
<wsdl:输入消息=“intf:getCreditHistoryLengthRequest”
名称=“getCreditHistoryLengthRequest”/>
<wsdl:输出消息=“intf:getCreditHistoryLengthResponse”
名称=“getCreditHistoryLengthResponse”/>
</wsdl:操作>
<wsdl:操作名称=“getCreditScore”parameterOrder=“de_ssn”>
<wsdl:输入消息=“intf:getCreditScoreRequest”
名称=“getCreditScoreRequest”/>
<wsdl:输出消息=“intf:getCreditScoreResponse”
名称=“getCreditScoreResponse”/>
</wsdl:操作>
</wsdl:端口类型>
<wsdl:binding name="CreditAgencyWSSoapBinding" type="intf:CreditAgencyWS">
...
</wsdl:绑定>
<wsdl:服务名称=“CreditAgencyWSService”>
<wsdl:端口绑定 =“intf:CreditAgencyWSSoapBinding”名称 =“CreditAgencyWS”>
<wsdlsoap:地址
位置=“http://192.168.1.25:8080/axis/CreditAgencyWS.jws”/>
</wsdl:端口>
</wsdl:服务>
</wsdl:定义>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
<wsdl:message name="getCreditScoreResponse">
<wsdl:part name="getCreditScoreReturn" type="xsd:int"/>
</wsdl:message>
<wsdl:message name="getCreditHistoryLengthRequest">
<wsdl:part name="de_ssn" type="xsd:int"/>
</wsdl:message>
<wsdl:message name="getCreditScoreRequest">
<wsdl:part name="de_ssn" type="xsd:int"/>
</wsdl:message>
<wsdl:message name="getCreditHistoryLengthResponse">
<wsdl:part name="getCreditHistoryLengthReturn" type="xsd:int"/>
</wsdl:message>
<wsdl:portType name="CreditAgencyWS">
<wsdl:operation name="getCreditHistoryLength" parameterOrder="de_ssn">
<wsdl:input message="intf:getCreditHistoryLengthRequest"
name="getCreditHistoryLengthRequest"/>
<wsdl:output message="intf:getCreditHistoryLengthResponse"
name="getCreditHistoryLengthResponse"/>
</wsdl:operation>
<wsdl:operation name="getCreditScore" parameterOrder="de_ssn">
<wsdl:input message="intf:getCreditScoreRequest"
name="getCreditScoreRequest"/>
<wsdl:output message="intf:getCreditScoreResponse"
name="getCreditScoreResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="CreditAgencyWSSoapBinding" type="intf:CreditAgencyWS">
...
</wsdl:binding>
<wsdl:service name="CreditAgencyWSService">
<wsdl:port binding="intf:CreditAgencyWSSoapBinding" name="CreditAgencyWS">
<wsdlsoap:address
location="http://192.168.1.25:8080/axis/CreditAgencyWS.jws"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
<wsdl:service>元素定义CreditAgencyWSService 并公开将由客户端(在本例中为贷款经纪人)访问的端点。< wsdl:operation>元素定义CreditAgencyWS.jws文件中定义的两个公共方法getCreditScore和getCreditHistoryLength。对于每个方法,都定义了一个请求-响应消息对,如<wsdl:message>元素中所示。这些是getCreditScoreRequest 、 getCreditScoreResponse 、 getCreditHistoryLengthRequest和getCreditHistoryLengthResponse 。
The <wsdl:service> element defines the CreditAgencyWSService and exposes the endpoint that will be accessed by the client, in this case the loan broker. The <wsdl:operation> elements define the two public methods defined in the CreditAgencyWS.jws file, getCreditScore, and getCreditHistoryLength. For each of these methods, a request-response message pair is defined, as can be seen in the <wsdl:message> elements. These are getCreditScoreRequest, getCreditScoreResponse, getCreditHistoryLengthRequest, and getCreditHistoryLengthResponse.
出于空间考虑,我们再次折叠了 WSDL 文件的大部分元素以适应该部分的格式。如果您有兴趣查看整个文件,可以使用以下 URL 在您的服务器上访问它,其中主机名和端口号是您的服务器安装的值:
Once again, in the interest of space, we have collapsed most of the elements of the WSDL file to fit the format of the section. If you are interested in seeing the entire file, it can be accessed on your server using the following URL, where hostname and portnumber are the values for your server installation:
http://主机名:端口号/axis/CreditAgencyWS.jws?wsdl
http://hostname:portnumber/axis/CreditAgencyWS.jws?wsdl
我们示例中的信用机构 Web 服务不提供真正的功能。相反,它会删除实现并返回可供应用程序其他部分使用的虚拟数据。
The credit agency Web service in our example does not provide real functionality. Rather, it stubs out the implementation and returns dummy data that can be used by other parts of the application.
丰富了客户数据后,贷款经纪人现在调用贷款清算所功能,该功能是贷款经纪人本身的一部分。如以下调用所示,贷款清算所接收贷款申请的所有数据。
Having enriched the customer data, the loan broker now calls the loan clearinghouse function, which is part of the loan broker itself. As seen in the following call, the loan clearinghouse receives all the data for the loan application.
getResultsFromLoanClearingHouse(ssn, 贷款金额, 贷款期限,
信用历史长度、信用分数);
getResultsFromLoanClearingHouse(ssn, loanamount, loanduration,
credit_history_length, credit_score);
如果应用程序变得更加复杂或者如果贷款经纪人要求更改为使用外部贷款清算所,则贷款清算所功能可以进一步划分为自己的逻辑单元。在我们的示例中,贷款清算所功能可以分为三个步骤:
The loan clearinghouse function could be further demarcated into its own logical unit if the application gets more complex or if the loan broker requirements change to use an external loan clearinghouse. In our example, the loan clearinghouse functionality can be broken down into three steps:
获取可以为客户贷款提供服务的贷方列表。
Get a list of lenders who can service the customer loan.
从贷方列表中每家银行的所有报价中获取最佳报价。
Get the best quote out of all quotes from each bank in the lender list.
对最佳报价中的数据进行格式化并将其返回给贷款经纪人。
Format the data from the best quote and return it to the loan broker.
代码显示了以下步骤:
The code shows these steps:
私有字符串 getResultsFromLoanClearingHouse(int ssn, 双贷款金额,
int 贷款期限、int 信用历史长度、int 信用分数) {
String lch_results="贷款清算所结果";
ArrayList 贷方列表 = LenderGateway.getLenderList
(贷款金额、信用历史长度、信用分数);
BankQuote bestquote = BankQuoteGateway.getBestQuote
(贷方列表、SSN、贷款金额、贷款期限、信用历史长度、信用分数);
lch_results = "总共" + lenderlist.size() +
“引用,最好的引用来自”+
this.getLoanQuotesReport(bestquote);
返回lch_结果;
}
private String getResultsFromLoanClearingHouse(int ssn, double loanamount,
int loanduration, int credit_history_length, int credit_score) {
String lch_results="Results from Loan Clearing House ";
ArrayList lenderlist = LenderGateway.getLenderList
(loanamount, credit_history_length, credit_score);
BankQuote bestquote = BankQuoteGateway.getBestQuote
(lenderlist,ssn,loanamount,loanduration,credit_history_length,credit_score);
lch_results = "Out of a total of " + lenderlist.size() +
" quote(s), the best quote is from" +
this.getLoanQuotesReport(bestquote);
return lch_results;
}
贷款清算所的首要要求是获取可以为客户贷款申请提供服务的合适贷方列表。为了从贷款经纪人中抽象出贷款人选择过程的功能,我们创建了一个LenderGateway类。我们还可以通过将此功能封装在 Web 服务中来使解决方案变得更有趣。然而,Web 服务应该仔细设计,并且应该考虑参数和返回类型,因为这些类型必须可序列化到网络和从网络序列化。为了简单起见,我们将贷方选择逻辑合并到一个由贷款经纪人调用的简单 Java 类中。
The first requirement for the loan clearinghouse is to get a list of suitable lenders that can service the customer loan application. In order to abstract the functions of the lender selection process from the loan broker, we create a LenderGateway class. We could make the solution more interesting by encapsulating this function inside a Web service as well. However, Web services should be designed carefully, and the parameters and return types should be taken into consideration, since the types have to be serializable to and from the network. To keep things simple, we incorporate the lender selection logic in a simple Java class that is called by the loan broker.
如果贷方网关返回银行的服务端点集合,那么对于贷款经纪人来说将是最方便的。这种方法的缺点是银行 Web 服务的标识必须在贷方网关中进行硬编码。这在现实生活中成为维护噩梦,尤其是因为银行被收购、出售或合并的频率比 IT 架构师更换工作的频率还要高。我们将在本节后面讨论 BankQuoteGateway主题时讨论针对银行及其 Web 服务的强大解决方案。
It would be most convenient for the loan broker if the lender gateway returned a collection of service endpoints for the banks. The drawback of this approach is that the identification of the bank Web service would have to be hard-coded in the lender gateway. This becomes a maintenance nightmare in real life, especially since banks are bought, sold, or merged more often than IT architects change jobs. We discuss a robust solution for the bank and its Web service in the discussion of the BankQuoteGateway topic later in this section.
getLenderList方法(如下所示)返回可以为贷款请求提供服务的贷方(即银行)集合。
The getLenderList method, shown below, returns the set of lenders (i.e., banks) that can service the loan request.
公共静态ArrayList getLenderList(双贷款金额,
int 信用历史长度,
int 信用分数){
ArrayList 贷方 = new ArrayList();
LenderGateway.readProps();
if ((贷款金额 >= (double)75000) && (credit_score >= 600) &&
(credit_history_length >= 8))
{
lenders.add(new Bank1(主机名, 端口号));
lenders.add(new Bank2(主机名, 端口号));
}
if (((贷款金额 >= (double)10000) && (贷款金额 <= (double)74999)) &&
(credit_score >= 400) && (credit_history_length >= 3))
{
lenders.add(new Bank3(主机名, 端口号));
lenders.add(new Bank4(主机名, 端口号));
}
lenders.add(new Bank5(主机名, 端口号));
返回贷方;
}
public static ArrayList getLenderList(double loanamount,
int credit_history_length,
int credit_score){
ArrayList lenders = new ArrayList();
LenderGateway.readProps();
if ((loanamount >= (double)75000) && (credit_score >= 600) &&
(credit_history_length >= 8))
{
lenders.add(new Bank1(hostname, portnum));
lenders.add(new Bank2(hostname, portnum));
}
if (((loanamount >= (double)10000) && (loanamount <= (double)74999)) &&
(credit_score >= 400) && (credit_history_length >= 3))
{
lenders.add(new Bank3(hostname, portnum));
lenders.add(new Bank4(hostname, portnum));
}
lenders.add(new Bank5(hostname, portnum));
return lenders;
}
该方法实现了收件人列表模式。在我们的示例中,规则库由非常简单的if语句组成,这些语句根据一组预定义的条件选择一个或多个银行。我们还为每个客户请求设置了默认选择,以便每个客户请求都至少有一个报价。
This method implements the Recipient List pattern. In our example, the rule base consists of very simple if statements that choose one or more banks based on a set of predefined conditions. We have also set a default selection for every customer request so that every customer request will have at least one quote.
贷款经纪人现在将贷方列表传递到银行报价网关,以开始收集报价并做出选择。我们创建另一个网关,一个名为BankQuoteGateway 的类来抽象银行接口的内部功能。贷款经纪人所需要做的就是向 BankQuoteGateway 请求最佳报价,如以下方法调用所示:
The loan broker now passes the list of lenders to the bank quote gateway to start gathering quotes and make a selection. We create another gateway, a class named BankQuoteGateway to abstract the internal functioning of the bank interface. All the loan broker needs to do is request the best quote from the BankQuoteGateway, as shown in the following method call:
BankQuote bestquote = BankQuoteGateway.getBestQuote(lenderlist, ssn, 贷款金额,
贷款期限、信用历史长度、信用分数);
BankQuote bestquote = BankQuoteGateway.getBestQuote(lenderlist, ssn, loanamount,
loanduration,credit_history_length,credit_score);
BankQuoteGateway通过获取所有银行的报价来响应贷款经纪人的请求,然后选择最佳报价(即利率最低的报价)。getBestQuote方法如下所示:
The BankQuoteGateway responds to the loan broker request by getting the quotes from all the banks and then selecting the best quote (i.e., the quote with the lowest rate). The getBestQuote method is shown here:
公共静态BankQuote getBestQuote(ArrayList贷方,int ssn,双贷款金额,
国际贷款期限,
int 信用历史长度,
int 信用分数){
银行报价最低报价 = null;
银行报价当前报价 = null;
ArrayList 银行报价 = BankQuoteGateway.getBankQuotes(贷方, ssn, 贷款金额,
贷款期限、信用历史长度、信用分数);
迭代器 allquotes =bankquotes.iterator();
while (allquotes.hasNext()){
if (最低报价 == null){
最低报价 = (BankQuote)allquotes.next();
}
别的{
当前报价 = (BankQuote)allquotes.next();
if (当前报价.getInterestRate() < 最低报价.getInterestRate()){
最低报价 = 当前报价;
}
}
}
返回最低报价;
}
public static BankQuote getBestQuote(ArrayList lenders, int ssn, double loanamount,
int loanduration,
int credit_history_length,
int credit_score){
BankQuote lowestquote = null;
BankQuote currentquote = null;
ArrayList bankquotes = BankQuoteGateway.getBankQuotes(lenders, ssn, loanamount,
loanduration, credit_history_length, credit_score);
Iterator allquotes = bankquotes.iterator();
while (allquotes.hasNext()){
if (lowestquote == null){
lowestquote = (BankQuote)allquotes.next();
}
else{
currentquote = (BankQuote)allquotes.next();
if (currentquote.getInterestRate() < lowestquote.getInterestRate()){
lowestquote = currentquote;
}
}
}
return lowestquote;
}
前面的代码中最重要的一行是对 getBankQuotes 的调用。 该方法不仅执行受控拍卖,还实现了聚合器模式。以下清单显示了getBankQuotes方法:
The most significant line in the preceeding code is the call to getBankQuotes. This method not only performs the controlled Auction but also implements the Aggregator pattern. The following listing shows the getBankQuotes method:
公共静态ArrayList getBankQuotes(ArrayList贷方,int ssn,双贷款金额,
国际贷款期限,
int 信用历史长度,
int 信用分数) {
ArrayList 银行报价 = new ArrayList();
BankQuote 银行报价 = null;
银行银行=空;
迭代器银行列表=贷方.iterator();
while (banklist.hasNext()){
银行 = (银行)banklist.next();
银行报价 = 银行.getBankQuote(ssn, 贷款金额, 贷款期限,
信用历史长度、信用分数);
银行报价.add(银行报价);
}
返回银行报价单;
}
public static ArrayList getBankQuotes(ArrayList lenders, int ssn, double loanamount,
int loanduration,
int credit_history_length,
int credit_score) {
ArrayList bankquotes = new ArrayList();
BankQuote bankquote = null;
Bank bank = null;
Iterator banklist = lenders.iterator();
while (banklist.hasNext()){
bank = (Bank)banklist.next();
bankquote = bank.getBankQuote(ssn, loanamount, loanduration,
credit_history_length, credit_score);
bankquotes.add(bankquote);
}
return bankquotes;
}
受控拍卖的功能是通过while循环实现的。它从贷方列表中提取每家银行,然后调用该方法来生成银行报价,如下面的代码中突出显示的那样。请特别注意方法调用中参数列表的顺序,我们将在开始设计银行和相关 Web 服务时解释其意义。
The functionality of a controlled auction is implemented by the while loop. It extracts each bank from the lender list, and then invokes the method to generate a bank quote, as highlighted in the code below. Pay special attention to the order of the parameter list in the call to the method, and we will explain the significance when we start designing the banks and the associated Web services.
银行.getBankQuote(ssn, 贷款金额, 贷款期限, 信用历史长度, 信用分数);
bank.getBankQuote(ssn, loanamount, loanduration, credit_history_length, credit_score);
使用银行报价 ArrayList 聚合响应。 getBestQuote方法迭代此银行报价集合并选择最低报价,并将其发送回贷款经纪人。
The response is aggregated using the bankquotes ArrayList. The getBestQuote method iterates over this collection of bank quotes and selects the lowest quote, which is sent back to the loan broker.
如前所述,我们将设计银行和银行 Web 服务来模拟现实世界的银行运营,并将银行的功能分开,而不是与贷款经纪人的功能紧密耦合。这将使我们的银行类具有使用前面在 CreditAgencyGateway 类中描述的网关模式的优点。
As mentioned earlier, we will design the bank and bank Web service to emulate a real-world bank operation and keep the functions of the bank separate, not tightly coupled with the functions of the loan broker. This will let our bank classes have the advantages of using the Gateway pattern described earlier in the CreditAgencyGateway class.
我们定义一个抽象Bank类如下:
We define an abstract Bank class as follows:
公共抽象类银行{
字符串银行名称;
字符串端点=“”;
双倍优惠利率;
公共银行(字符串主机名,字符串端口号){
this.银行名称 = "";
this.prime_rate = 3.5;
}
公共无效setEndPoint(字符串endpt){this.endpoint = endpt;}
public String getBankName(){return this.bankname;}
public String getEndPoint(){return this.endpoint;}
公共双 getPrimeRate(){return this.prime_rate;}
公共摘要 BankQuote getBankQuote(int ssn,双倍贷款金额,int 贷款期限,
int 信用历史记录长度、int 信用分数);
公共无效任意等待(){
尝试{
Thread.sleep((int)(Math.random()*10)*100);
}catch(java.lang.InterruptedException intex){
intex.printStackTrace();
}
}
}
public abstract class Bank {
String bankname;
String endpoint = "";
double prime_rate;
public Bank(String hostname, String portnum){
this.bankname = "";
this.prime_rate = 3.5;
}
public void setEndPoint(String endpt){this.endpoint = endpt;}
public String getBankName(){return this.bankname;}
public String getEndPoint(){return this.endpoint;}
public double getPrimeRate(){return this.prime_rate;}
public abstract BankQuote getBankQuote(int ssn, double loanamount, int loanduration,
int credit_history_length, int credit_score);
public void arbitraryWait(){
try{
Thread.sleep((int)(Math.random()*10)*100);
}catch(java.lang.InterruptedException intex){
intex.printStackTrace();
}
}
}
在我们的示例中,从银行获取报价的过程大致按照与现实世界银行运营相同的方式进行建模。首先,完成少量的文书工作,然后访问计算机化的汇率报价系统,然后在报价返回到 BankQuoteGateway 之前完成额外的文书工作。
In our example, the process for getting a quote from a bank is modeled roughly along the same lines a real-world bank operates. First, a small amount of clerical work is done, followed by an access to the computerized rate quote system, and then additional clerical work is done before the quote is returned to the BankQuoteGateway.
Bank抽象类和子银行类(Bank1到Bank5)对常规银行的操作进行建模。在我们的例子中,银行收到贷款请求,职员进行尽职调查,同时验证客户信息是否准确。我们选择将文书工作建模为在Bank抽象类中实现的任意等待方法(如刚才所示),并在getBankQuote方法中调用。为了让事情变得有趣,我们使用 Web 服务对银行的报价系统进行建模,以获取报价。对于给定的银行(银行 n),报价系统使用 BanknWS并在名为的文件中编码银行nWS.jws。有五个银行类别(Bank1到Bank5)和五个报价系统(Bank1WS到Bank5WS)。每个报价系统在方法调用中使用不同的参数列表格式,就像在现实生活中,不同的银行可能使用不同的数据格式一样。这意味着Bank类在调用 Web 服务之前必须使用消息转换器来转换消息的格式。我们将在讨论银行课程后展示这一点。
The Bank abstract class and the child bank classes (Bank1 to Bank5) model the operation of a regular bank. In our example, the bank receives the loan request, and the clerical staff conducts the due diligence research while verifying that the customer information is accurate. We chose to model the clerical work as an arbitrary wait method implemented in the Bank abstract class, as just shown, and invoked in the getBankQuote method. To make things interesting, we use Web services to model the bank's rate quote system for getting rate quotes. For a given bank (bank n), the rate quote system is modeled using a BanknWS and coded in a file named BanknWS.jws. There are five bank classes (Bank1 to Bank5) and five rate quote systems (Bank1WS to Bank5WS). Each of the rate quote systems uses a different format for the parameter list in the method calljust like, in real life, different banks are likely to use different data formats. This means the Bank class has to use a Message Translator to translate the format of the message before calling the Web service. We will show this after discussing the Bank classes.
请注意,抽象Bank类中的getBankQuote方法是一个抽象方法,并且具有按特定格式排序的参数。我们现在看看其中一个银行实现,并且没有特殊原因,选择Bank1。所有银行的类结构都是相同的,每个银行的区别仅在于其字段的值(银行名称和端点地址),这些字段是在构建银行对象时设置的。
Note that the getBankQuote method in the abstract Bank class is an abstract method and has the parameters ordered in a particular format. We now look at one of the bank implementations and, for no particular reason, choose Bank1. The class structure of all the banks will be identical, and each will differ only in the values of its fields (the bank name and endpoint address), which are set when the bank object is constructed.
公共类 Bank1 扩展了 Bank {
公共 Bank1(字符串主机名,字符串端口号){
超级(主机名,端口号);
Bankname = "乡村俱乐部专属银行家\n";
String ep1 = "http://" + 主机名 + ":" + 端口号 + "/axis/Bank1WS.jws";
this.setEndPoint(ep1);
}
公共无效setEndPoint(字符串endpt){this.endpoint = endpt;}
public String getBankName(){return this.bankname;}
public String getEndPoint(){return this.endpoint;}
public BankQuote getBankQuote(int ssn, 双倍贷款金额, int 贷款期限,
int Credit_history_length, int Credit_score) {
BankQuote 银行报价 = new BankQuote();
整数 i1 = new Integer(ssn);
双倍 i2 = new Double(prime_rate);
Double i3 = new Double(贷款金额);
整数 i4 = new Integer(loanduration);
整数 i5 = new Integer(credit_history_length);
整数 i6 = new Integer(credit_score);
尝试{
服务service = new Service();
呼叫 call = (呼叫) service.createCall();
call.setTargetEndpointAddress( new java.net.URL(endpoint) );
call.setOperationName("getQuote");
call.addParameter( "op1", XMLType.XSD_INT, ParameterMode.IN );
call.addParameter( "op2", XMLType.XSD_DOUBLE, ParameterMode.IN );
call.addParameter( "op3", XMLType.XSD_DOUBLE, ParameterMode.IN );
call.addParameter( "op4", XMLType.XSD_INT, ParameterMode.IN );
call.addParameter( "op5", XMLType.XSD_INT, ParameterMode.IN );
call.addParameter( "op6", XMLType.XSD_INT, ParameterMode.IN );
call.setReturnType( XMLType.XSD_DOUBLE);
双倍利率 = (Double) call.invoke( new Object [] {i1,i2,i3,i4,i5,i6});
银行报价.setBankName(银行名称);
银行报价.setInterestRate(interestrate.doubleValue());
}catch(异常前){
System.err.println("从 " + 银行名称访问轴 Web 服务时出错);
BankQuote badbq = new BankQuote();
badbq.setBankName("WS 中出现错误");
返回badbq;
}
任意等待();
返回银行报价单;
}
}
public class Bank1 extends Bank {
public Bank1(String hostname, String portnum){
super(hostname,portnum);
bankname = "Exclusive Country Club Bankers\n";
String ep1 = "http://" + hostname + ":" + portnum + "/axis/Bank1WS.jws";
this.setEndPoint(ep1);
}
public void setEndPoint(String endpt){this.endpoint = endpt;}
public String getBankName(){return this.bankname;}
public String getEndPoint(){return this.endpoint;}
public BankQuote getBankQuote(int ssn, double loanamount, int loanduration,
int credit_history_length, int credit_score) {
BankQuote bankquote = new BankQuote();
Integer i1 = new Integer(ssn);
Double i2 = new Double(prime_rate);
Double i3 = new Double(loanamount);
Integer i4 = new Integer(loanduration);
Integer i5 = new Integer(credit_history_length);
Integer i6 = new Integer(credit_score);
try{
Service service = new Service();
Call call = (Call) service.createCall();
call.setTargetEndpointAddress( new java.net.URL(endpoint) );
call.setOperationName("getQuote");
call.addParameter( "op1", XMLType.XSD_INT, ParameterMode.IN );
call.addParameter( "op2", XMLType.XSD_DOUBLE, ParameterMode.IN );
call.addParameter( "op3", XMLType.XSD_DOUBLE, ParameterMode.IN );
call.addParameter( "op4", XMLType.XSD_INT, ParameterMode.IN );
call.addParameter( "op5", XMLType.XSD_INT, ParameterMode.IN );
call.addParameter( "op6", XMLType.XSD_INT, ParameterMode.IN );
call.setReturnType( XMLType.XSD_DOUBLE);
Double interestrate = (Double) call.invoke( new Object [] {i1,i2,i3,i4,i5,i6});
bankquote.setBankName(bankname);
bankquote.setInterestRate(interestrate.doubleValue());
}catch(Exception ex){
System.err.println("Error accessing the axis webservice from " + bankname);
BankQuote badbq = new BankQuote();
badbq.setBankName("ERROR in WS");
return badbq;
}
arbitraryWait();
return bankquote;
}
}
如前面的代码所示,已实现getBankQuote方法并具有按特定顺序排列的参数。
As seen in the preceeding code, the getBankQuote method is implemented and has the parameters in a particular order.
public BankQuote getBankQuote(int ssn, 双倍贷款金额, int 贷款期限,
int 信用历史长度、int 信用分数)
public BankQuote getBankQuote(int ssn, double loanamount, int loanduration,
int credit_history_length, int credit_score)
如前所述,每个银行的报价系统参数的格式是不同的。这意味着Bank类的getBankQuote方法实现了消息转换器模式,并且必须在调用相应的银行 Web 服务之前转换参数的顺序。每个银行 Web 服务的getQuote方法的签名如下所示。
As described earlier, the format for the parameters of the rate quote system for each bank is different. This means the getBankQuote method of the Bank class implements the Message Translator pattern and has to translate the order of the parameters before calling the respective bank Web service. The signature of the method getQuote for each bank Web service is shown below.
银行1WS:
Bank1WS:
getQuote(int ssn, 双 prime_rate, 双贷款金额, int 贷款期限,
int 信用历史长度、int 信用分数)
getQuote(int ssn, double prime_rate, double loanamount, int loanduration,
int credit_history_length, int credit_score)
银行2WS:
Bank2WS:
getQuote(双倍 prime_rate, 双倍贷款金额, int 贷款期限,
int 信用历史长度、int 信用分数、int ssn)
getQuote(double prime_rate, double loanamount, int loanduration,
int credit_history_length, int credit_score, int ssn)
银行3WS:
Bank3WS:
getQuote(双贷款金额, int 贷款期限, int 信用历史长度,
int 信用分数、int ssn、双倍 prime_rate)
getQuote(double loanamount, int loanduration, int credit_history_length,
int credit_score, int ssn, double prime_rate)
银行4WS:
Bank4WS:
getQuote(int 贷款期限、int 信用历史长度、int 信用分数、int ssn、
双倍优惠利率,双倍贷款金额)
getQuote(int loanduration, int credit_history_length, int credit_score, int ssn,
double prime_rate, double loanamount)
银行5WS:
Bank5WS:
getQuote(int Credit_history_length, int Credit_score, int ssn, double prime_rate,
双倍贷款金额,国际贷款期限)
getQuote(int credit_history_length, int credit_score, int ssn, double prime_rate,
double loanamount, int loanduration)
getQuote方法的实际实现是一个占位符,并使用简单的算法返回报价,如下所示的Bank1WS.jws。
The actual implementation of the getQuote method is a placeholder and returns a rate quote using a simple algorithm, as shown below for Bank1WS.jws.
公共类 Bank1WS {
公共双 getQuote(int ssn, 双 prime_rate, 双贷款金额,
int 贷款期限、int 信用历史长度、int 信用分数)
{
双倍费率溢价 = 1.5;
double int_rate = prime_rate + 费率premium + (double)(loanduration/12)/10 +
(双)(Math.random()*10)/10;
返回int_rate;
}
}
public class Bank1WS {
public double getQuote(int ssn, double prime_rate, double loanamount,
int loanduration, int credit_history_length, int credit_score)
{
double ratepremium = 1.5;
double int_rate = prime_rate + ratepremium + (double)(loanduration/12)/10 +
(double)(Math.random()*10)/10;
return int_rate;
}
}
在实际应用中,该公式更加详细和复杂。getQuote方法的返回类型是一个双精度数字,表示银行根据贷款申请中的参数向客户提供的利率。
In a real-world application, the formula is a lot more detailed and complicated. The return type of the getQuote method is a double precision number representing the rate the bank offers the customer, given the parameters in the loan application.
Axis 服务器再次自动为每个 Bank JWS 文件生成一个 WSDL 文件(在http://hostname:portnum/axis/Bank1WS.jws?wsdl中公开)。其他银行的 WSDL 文件将具有类似的格式,但参数的定义有所不同。Bank1WS.jws Web 服务的 WSDL 文件如下所示。
Once again, the Axis server automatically generates a WSDL file for each Bank JWS file (exposed at http://hostname:portnum/axis/Bank1WS.jws?wsdl). The WSDL files for the other banks will have a similar format but will differ in the definition of the parameters. The WSDL file for the Bank1WS.jws Web service is shown below.
<wsdl:定义 xmlns:wsdl =“http://schemas.xmlsoap.org/wsdl/”
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
<wsdl:消息名称=“getQuoteRequest”>
<wsdl:part name="ssn" type="xsd:int"/>
<wsdl:part name="prime_rate" type="xsd:double"/>
<wsdl:part name="loanamount" type="xsd:double"/>
<wsdl:part name="loanduration" type="xsd:int"/>
<wsdl:part name="credit_history_length" type="xsd:int"/>
<wsdl:part name="credit_score" type="xsd:int"/>
</wsdl:消息>
<wsdl:消息名称=“getQuoteResponse”>
<wsdl:part name="getQuoteReturn" type="xsd:double"/>
</wsdl:消息>
<wsdl:portType name="Bank1WS">
<wsdl:操作名称=“getQuote”parameterOrder=“ssn prime_rate贷款金额贷款期限”
信用历史长度 信用分数">
<wsdl:input message="intf:getQuoteRequest" name="getQuoteRequest"/>
<wsdl:output message="intf:getQuoteResponse" name="getQuoteResponse"/>
</wsdl:操作>
</wsdl:端口类型>
<wsdl:binding name="Bank1WSSoapBinding" type="intf:Bank1WS">
...
</wsdl:绑定>
<wsdl:服务名称=“Bank1WSService”>
<wsdl:端口绑定 =“intf:Bank1WSSoapBinding”名称 =“Bank1WS”>
<wsdlsoap:地址位置=“http://192.168.1.25:8080/axis/Bank1WS.jws”/>
</wsdl:端口>
</wsdl:服务>
</wsdl:定义>
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/">
<wsdl:message name="getQuoteRequest">
<wsdl:part name="ssn" type="xsd:int"/>
<wsdl:part name="prime_rate" type="xsd:double"/>
<wsdl:part name="loanamount" type="xsd:double"/>
<wsdl:part name="loanduration" type="xsd:int"/>
<wsdl:part name="credit_history_length" type="xsd:int"/>
<wsdl:part name="credit_score" type="xsd:int"/>
</wsdl:message>
<wsdl:message name="getQuoteResponse">
<wsdl:part name="getQuoteReturn" type="xsd:double"/>
</wsdl:message>
<wsdl:portType name="Bank1WS">
<wsdl:operation name="getQuote" parameterOrder="ssn prime_rate loanamount loanduration
credit_history_length credit_score">
<wsdl:input message="intf:getQuoteRequest" name="getQuoteRequest"/>
<wsdl:output message="intf:getQuoteResponse" name="getQuoteResponse"/>
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="Bank1WSSoapBinding" type="intf:Bank1WS">
...
</wsdl:binding>
<wsdl:service name="Bank1WSService">
<wsdl:port binding="intf:Bank1WSSoapBinding" name="Bank1WS">
<wsdlsoap:address location="http://192.168.1.25:8080/axis/Bank1WS.jws"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
正如我们所看到的,WSDL 包含一个操作getQuote ,它在<wsdl:operation>元素中定义,Axis 将其映射到Bank1WS.jws类中的getQuote方法。<wsdl:service>元素定义 Web 服务Bank1WS。有一个请求-响应消息对,每个消息对都在<wsdl:message>元素中定义:getQuoteRequest 和getQuoteResponse 。
As we can see, the WSDL contains one operation, getQuote, defined in the <wsdl:operation> element, which Axis maps to the getQuote method in the Bank1WS.jws class. The <wsdl:service> element defines the Web service Bank1WS. There is a request-response message pair, each defined in a <wsdl:message> element: getQuoteRequest and getQuoteResponse.
每个银行的利率报价都在银行报价 bean ( BankQuote )中设置,该 bean 将添加到发送回 BankQuoteGateway 的集合中。 该 bean 不需要任何格式,并且所有银行返回的 bean 看起来基本相同。这消除了规范器将回复消息转换为通用格式的需要。
The rate quote for each bank is set in a bank quote bean (BankQuote), which is added to the collection sent back to the BankQuoteGateway. The bean does not require any formatting, and the beans returned by all the banks look essentially the same. This eliminates the need for a Normalizer to convert the reply messages to a common format.
BankQuoteGateway从它返回的银行报价集合中选择最低报价,并将一个银行报价 bean 发送回贷款经纪人。贷款经纪人访问 bean 中的数据并格式化报告以发送回客户端应用程序。格式化报告的方法如下所示。
The BankQuoteGateway selects the lowest quote from the collection of bank quotes it gets back and sends one bank quote bean back to the loan broker. The loan broker accesses the data in the bean and formats a report to send back to the client application. The method that formats the report is shown below.
私有静态字符串 getLoanQuotesReport(BankQuote bestquote){
String 银行名称 = bestquote.getBankName();
双倍最佳率 = ((双)((长)(bestquote.getInterestRate()*1000))/(双)1000);
String results = "\n银行名称:" + 银行名称 + "利率:" + bestrate;
返回结果;
}
private static String getLoanQuotesReport(BankQuote bestquote){
String bankname = bestquote.getBankName();
double bestrate = ((double)((long)(bestquote.getInterestRate()*1000))/(double)1000);
String results = "\nBank Name: " + bankname + "Interest Rate: " + bestrate;
return results;
}
客户端应用程序是客户与贷款经纪人应用程序的接口。客户的主要要求是在具有充分错误检查的功能用户界面环境中从客户收集信息。在远离客户视线的幕后,客户端应用程序准备三部分数据以传送到服务器应用程序的端点。为简单起见,我们将客户端应用程序设计为一个 Java 类,其 main 方法将客户端信息作为命令行参数接收。实际上,用户界面可以实现为基于窗口的胖客户端或基于浏览器的瘦客户端。客户端也可以是部署了客户端编程模型的另一个业务系统的一部分。
The client application is the customer's interface to the loan broker application. The main requirement of the client is to gather information from the customer in a functional user interface environment with adequate error checking. Beneath the covers and far away from the eyes of the customer, the client application prepares the three pieces of data for delivery to the endpoint of the server application. For simplicity, we designed the client application to be a Java class with a main method that takes in the client information as command-line arguments. In reality, the user interface could be implemented as a window-based fat client or a browser-based thin client. The client could also be part of another business system that has the client-programming model deployed. The most significant part of the client application with respect to invoking the loan broker Web service is shown here:
服务service = new Service();
呼叫 call = (呼叫) service.createCall();
call.setTargetEndpointAddress( new java.net.URL(endpoint) );
call.setOperationName(“getLoanQuote”);
call.addParameter( "op1", XMLType.XSD_INT, ParameterMode.IN );
call.addParameter( "op2", XMLType.XSD_DOUBLE, ParameterMode.IN );
call.addParameter( "op3", XMLType.XSD_INT, ParameterMode.IN );
call.setReturnType( XMLType.XSD_STRING );
String ret = (String) call.invoke( new Object [] {ssn, 贷款金额, 贷款期限});
Service service = new Service();
Call call = (Call) service.createCall();
call.setTargetEndpointAddress( new java.net.URL(endpoint) );
call.setOperationName( "getLoanQuote" );
call.addParameter( "op1", XMLType.XSD_INT, ParameterMode.IN );
call.addParameter( "op2", XMLType.XSD_DOUBLE, ParameterMode.IN );
call.addParameter( "op3", XMLType.XSD_INT, ParameterMode.IN );
call.setReturnType( XMLType.XSD_STRING );
String ret = (String) call.invoke( new Object [] {ssn, loanamount, loanduration});
代码中的重点是定义方法名称 ( getLoanQuotes )的行以及设置参数和返回类型的行。getLoanQuotes方法接收客户 ID、贷款金额和贷款期限。返回值是一个字符串。
The salient points in the code are the lines that define the method name (getLoanQuotes) and those that set the parameters and return types. The method getLoanQuotes takes in a customer ID, loan amount, and loan duration. The return value is a string.
由于我们没有使用 UDDI(Web 服务查找服务),因此我们对 Web 服务的端点 URL 进行硬编码。由于我们选择将贷款经纪人应用程序部署为 JWS 文件,因此端点具有标准格式,如 Axis API 为 JWS 定义的那样。我们部署的端点显示在以下 URL 中,其中主机名和端口号是与您的服务器安装相对应的值,
Since we are not using UDDI, the Web services lookup service, we hard-code the endpoint URL for our Web service. Since we chose to deploy the loan broker application as a JWS file, the endpoint has the standard format, as defined by the Axis API for a JWS. The endpoint for our deployment is shown in the following URL, where hostname and portnumber are the values corresponding to your server installation,
http://主机名:端口号/axis/LoanBroker.jws
http://hostname:portnumber/axis/LoanBroker.jws
一直被阻止等待响应的客户端应用程序将接受格式化报告并将其显示在为其设置的 GUI 区域中。或者,可以将其保存或发送到打印机。
The client application, which was blocked all along waiting for the response, will accept the formatted report and display it in the GUI area set up for it. Alternatively, it can be saved or sent to a printer.
本节假设您已安装 Axis 和贷款经纪人应用程序。服务器现在需要重新启动或启动(如果未运行)。请按照 Tomcat 帮助文件中的文档了解启动或重新启动服务器的正确步骤。然后,您可以按照以下步骤验证 Tomcat 和 Apache Axis 是否已启动并正在运行:
This section assumes that you have Axis and the loan broker application installed. The server now needs to be restarted or started if it was not running. Follow the documentation in the Tomcat help files for the proper steps to start or restart the server. You can then verify that Tomcat and Apache Axis are up and running by following these steps:
在客户端计算机上打开 shell 或命令窗口并运行应用程序,如 UNIX/Linux 或 Microsoft Windows 所示:
Open a shell or command window on the client machine and run the application as shown for either UNIX/Linux or Microsoft Windows:
java -classpath %CLASSPATH% LoanQueryClient [客户 ID] [贷款金额] [贷款期限(以月为单位)]
java -classpath %CLASSPATH% LoanQueryClient [customerid] [loanamount] [loanduration in months]
或者
or
java -classpath $CLASSPATH LoanQueryClient [客户 ID] [贷款金额] [贷款期限(以月为单位)]
java -classpath $CLASSPATH LoanQueryClient [customerid] [loanamount] [loanduration in months]
例如,
For example,
java -classpath %CLASSPATH% LoanQueryClient 199 100000.00 29
java -classpath %CLASSPATH% LoanQueryClient 199 100000.00 29
这将调用贷款经纪人 Web 服务并返回以下结果:
This invokes the loan broker Web service and returns the following results:
通过 1053292919270 调用 LoanBroker Web 服务 LoanBroker 服务已回复 1053292925860 个刻度 运行查询的总时间 = 6590 毫秒 以下是来自贷款清算所的回复 ssn = 199 的客户请求贷款金额 = 100000.0 29 个月 客户的附加数据:信用评分和信用记录长度 信用评分= 756 信用记录长度= 12 所有回复银行的最佳报价详情如下: 总共 3 条报价中,最佳报价来自 银行名称:Exclusive Country Club Bankers 利率:6.197
Calling the LoanBroker webservice at 1053292919270 ticks LoanBroker service replied at 1053292925860 ticks Total time to run the query = 6590 milliseconds The following reply was received from the Loan Clearing House Client with ssn= 199 requests a loan of amount= 100000.0 for 29 months Additional data for customer: credit score and length of credit history Credit Score= 756 Credit History Length= 12 The details of the best quote from all banks that responded are shown below: Out of a total of 3 quote(s), the best quote is from Bank Name: Exclusive Country Club Bankers Interest Rate: 6.197
您可以通过在运行客户端时输入不同的贷款金额来测试贷款经纪人。我们现在将分析单个客户端运行的输出结果。后来,我们推出了多个客户端。
You can test the loan broker by entering different loan amounts when running the client. We will now analyze the output results with a single client running. Later, we launch multiple clients.
客户端应用程序在调用服务器上的贷款经纪人 Web 服务端点时跟踪时间。客户端还记录服务器响应结果的时间。客户端应用程序中会报告 Web 服务调用的开始时间和结束时间以及两者之间的差异。
The client application keeps track of the time when it invokes the loan broker Web service endpoint on the server. The client also notes the time when the server responds with the result. Both start and end times of the call to the Web service are reported in the client application together with the difference between the two.
通过 1053292919270 调用 LoanBroker Web 服务 LoanBroker 服务已回复 1053292925860 个刻度 运行查询的总时间 = 6590 毫秒
Calling the LoanBroker webservice at 1053292919270 ticks LoanBroker service replied at 1053292925860 ticks Total time to run the query = 6590 milliseconds
贷款清算所 Web 服务报告客户端应用程序通过网络发送的客户请求的所有相关详细信息。
The Loan Clearing House Web service reports all relevant details of the customer request sent across the network by the client application.
ssn = 199 的客户请求贷款金额 = 100000.0 29 个月
Client with ssn= 199 requests a loan of amount= 100000.0 for 29 months
贷款清算所还报告为支持客户请求而收集的其他信用数据。
The Loan Clearing House also reports additional credit data that was gathered to support the customer request.
客户的附加数据:信用评分和信用记录长度 信用评分= 756 信用记录长度= 12
Additional data for customer: credit score and length of credit history Credit Score= 756 Credit History Length= 12
LoanBroker分析数据并选择一组符合客户贷款请求标准的银行。LoanBroker以各银行要求的格式提交客户数据,并等待各银行响应。由于这是同步应用程序,贷款经纪人会阻塞,直到银行响应或请求超时或失败。
The LoanBroker analyzes the data and selects a set of banks that fit the customer loan request criteria. The LoanBroker submits the customer data in the format required for individual banks and waits for each bank to respond. Since this is a synchronous application, the loan broker blocks until the bank responds or the request times out or fails.
贷款经纪人收集所有回复,分析返回报价,并选择最佳报价。该报价经过格式化后与任何相关数据一起发送回客户。以下是所有回复银行的最佳报价的详细信息:
The LoanBroker collects all the responses, analyzes the return quotes, and chooses the best quote. This quote is formatted and sent back to the customer along with any relevant data. Here are the details of the best quote from all banks that responded:
总共 3 条报价中,最佳报价来自 银行名称:Exclusive Country Club Bankers 利率:6.197
Out of a total of 3 quote(s), the best quote is from Bank Name: Exclusive Country Club Bankers Interest Rate: 6.197
从前面的代码段中,我们看到LoanBroker报告三家银行做出了响应,并选择了最佳报价并将报价呈现给用户。
From the preceeding code segment, we see that the LoanBroker reports that three banks responded and that it selected the best quote and presented the quote to the user.
正如我们在本章前面讨论序列图时所解释的,获得响应报价的总时间很长,因为贷款经纪人必须等待每家银行的响应,然后才能将请求提交给贷方列表中的下一个银行。结果,客户在提交请求后需要等待很长时间才能得到结果报价。我们使用单个客户端针对服务器运行了多次基线测试,并获得了运行查询的平均总时间(大约 8 秒)。然后,我们在同一客户端计算机上的不同窗口中启动了四个客户端实例,并获得了以下平均时间:
As explained when we discussed the sequence diagram earlier in this chapter, the total time to get the response quote is significant since the loan broker has to wait for a response from each bank before submitting the request to the next bank in the lenders list. As a result, the customer has to wait a long time after submitting the request to get the result quote back. We ran several baseline tests with a single client against the server and obtained an average of the total time to run the query (about 8 seconds). We then launched four instances of the client in separate windows on the same client machine and obtained the following average times:
客户端1:12520毫秒 客户端2:12580毫秒 客户端3:15710毫秒 客户端4:13760毫秒
Client 1: 12520 milliseconds Client 2: 12580 milliseconds Client 3: 15710 milliseconds Client 4: 13760 milliseconds
虽然这些测试无论如何都不能被认为是科学的,但经验证据表明,当多个客户尝试同时访问我们的贷款经纪人系统时,性能会受到巨大影响。
While these tests could not be considered scientific by any stretch of imagination, the empirical evidence points to the fact that performance suffers tremendously when multiple clients try to simultaneously access our loan broker system.
为了使贷款经纪人示例的设计讨论更加容易,我们选择将所有 Web 服务实现为 JWS 文件。这为我们提供了通过简单地将服务复制到服务器来部署服务的优势。然而,缺点是服务类的新实例会为每个请求实例化,并在请求完成后立即释放。这意味着我们注意到的一些时间延迟是服务器在调用服务之前创建该类的实例的结果。
To make the discussion of the design of the loan broker example easier, we chose to implement all the Web services as JWS files. This gave us the advantage of deploying the service by simply copying it over to the server. The disadvantage, however, is that a new instance of the service class is instantiated for each request and is deallocated as soon as the request is complete. This means that some amount of the time lag we noticed is a result of the server creating instances of the class before invoking the service.
我们可以选择更复杂的路线并设计一个将使用 WSDD 文件进行部署的 Java 类文件。这将使我们能够灵活地定义实例化类的持续时间:整个客户端会话或应用程序的持续时间。在设计实际应用程序时,如何部署 Web 服务是一个重要的问题。如果我们选择包含部署问题,则示例的描述将变得非常冗长,并且对于本章的目的而言,设计细节将变得不必要的复杂。
We could have chosen the more complicated route and designed a Java class file that would get deployed using a WSDD file. This would give us the flexibility of defining how long the instantiated class would persist: for the entire client session or for the duration of the application. The issue of how to deploy a Web service is a significant one when designing a real-world application. If we had chosen to include the deployment issue, the description of the example would have become very lengthy, and the design details would have become unnecessarily complicated for the purposes of this chapter.
在本节中,我们逐步完成了使用同步 SOAP/HTTP Web 服务的贷款经纪人应用程序的实现。我们使用预测路由将贷款请求提交给一组银行。我们强调了使用这种方法的优点和缺点。我们做了一些设计权衡来管理讨论并避免陷入描述部署细节的困境。总体目的是就这种方法的优点和缺点进行讨论。我们还看到了本书中描述的许多模式的使用,这将有助于将同步预测方法应用于其他业务领域。
In this section we stepped through the implementation of the loan broker application using synchronous SOAP/HTTP Web services. We used predictive routing to submit our loan request to a set of banks. We highlighted the strengths and drawbacks of using this approach. We made some design trade-offs to manage the discussion and avoid getting bogged down in describing deployment details. The overall intention was to provide a discussion on the merits and demerits of this approach. We also saw the use of many of the patterns described in this book, which will help in adapting the synchronous predictive approach to other business domains.
本节介绍如何使用 Microsoft .NET、C# 和 MSMQ 实现贷款经纪人示例(请参阅本章的简介)。Microsoft .NET 框架包括System.Messaging命名空间,该命名空间使 .NET 程序能够访问最新版本的 Windows 操作系统(Windows 2000、Windows XP 和 Windows Server 2003)中包含的 Microsoft 消息队列服务。该示例演示了许多设计决策,并显示了使该解决方案发挥作用所需的实际代码。我们尽可能关注解决方案的设计方面,因此即使您不是核心 C# 开发人员,此示例也很有价值。事实上,除了System.Messaging 的实际接口之外,许多应用程序如果这个实现是用 Java 和 JMS 完成的,那么看起来会非常相似。
This section describes how to implement the loan broker example (see the introduction to this chapter) using Microsoft .NET, C#, and MSMQ. The Microsoft .NET framework includes the System.Messaging namespace that gives .NET programs access to the Microsoft Message Queuing Service included in the recent versions of the Windows operating system (Windows 2000, Windows XP, and Windows Server 2003). The example walks through many of the design decisions and shows the actual code required to make this solution work. As much as possible, we focus on the design aspects of the solution so that this example is of value even if you are not a hard-core C# developer. In fact, much of the application besides the actual interface into System.Messaging would look very similar if this implementation were done in Java and JMS.
通过使用 Microsoft BizTalk Server 等集成和编排工具,可以用更少的编码工作来实现此解决方案中演示的一些功能。我有意避免使用此类工具有两个原因。首先,这些工具不是免费的,您必须获得许可证才能运行这个简单的示例。其次,我想演示所有必要功能的显式实现。
Some of the functions demonstrated in this solution could likely be implemented with less coding effort by using an integration and orchestration tool such as Microsoft BizTalk Server. I intentionally avoided using such tools for two reasons. First, these tools are not free, and you would have to acquire a license just to run this simple example. Second, I wanted to demonstrate the explicit implementation of all necessary functions.
该解决方案被设置为多个可执行文件,以便不同的组件可以分布在多台计算机上。出于示例的目的,使用本地私人消息来保持设置要求简单并避免安装 Active Directory。因此,解决方案“按原样”必须在单台计算机上运行。
The solution is set up as multiple executables so that the different components can be distributed across multiple computers. For purpose of the example, local, private messages are used to keep the setup requirements simple and avoid having to install Active Directory. As a result, the solution "as is" must run on a single machine.
贷款经纪人示例的此实现使用消息队列上的异步消息传递。如示例概述中所述,这允许我们同时处理多个报价请求,但也要求我们在消息流经系统时将其关联起来,并最终生成贷款报价请求的响应消息。在本示例中,我们的许多设计决策都是由异步处理的需求驱动的。
This implementation of the loan broker example uses asynchronous messaging over message queues. As described in the example overview, this allows us to process multiple quote requests concurrently but also requires us to correlate messages as they flow though the system and ultimately produce a response message to the loan quote request. Many of our design decisions over the course of this example are driven by the need for asynchronous processing.
从外到内开始理解贷款经纪人的设计是个好主意。让我们首先检查贷款经纪人必须支持的所有外部接口(见图)。因为消息队列是单向的,所以我们需要一对队列来与另一个组件建立请求-响应通信(有关简单情况,请参阅第 6 章“插曲:简单消息传递”中的“.NET 请求/回复示例”部分)。结果,贷款经纪人在贷款请求队列上收到贷款报价请求,并在 上回复测试客户端loanReplyQueue。与信用局的交互发生在一对类似的队列上。不是为每个银行创建一对队列,银行回复队列。接收者列表将请求消息发送到每个单独的银行队列,而聚合器从到达 LoanReplyQueue 的回复消息中选择最佳报价。 接收者列表和聚合器一起充当分发式分散-聚集。为了简单起见,本例中的所有银行都使用相同的消息格式,以便规范化器不需要。但由于常见的银行消息格式与消费者期望的格式不同,我们仍然需要使用一个消息转换器将银行回复消息转换为贷款经纪人回复消息。我决定将贷款经纪人设计为流程经理。贷款经纪人不是将贷款经纪人内部的功能实现为由消息队列分隔的单独组件,而是在内部执行所有功能的单个组件。这种方法消除了在这些功能之间跨队列发送消息所产生的开销,但它要求贷款经纪人维护多个并发流程实例。
It is a good idea to start understanding the loan broker design from the outside in. Let's start by examining all external interfaces that the loan broker has to support (see figure). Because message queues are unidirectional, we need a pair of queues to establish a request-response communication with another component (see section ".NET Request/Reply Example" in Chapter 6, "Interlude: Simple Messaging," for a simple case). As a result, the loan broker receives requests for loan quotes on the loanRequestQueue and replies to the test client on the loanReplyQueue. The interaction with the credit bureau happens over a similar pair of queues. Rather than create a pair of queues for each bank, we decided to have all banks reply to the same bankReplyQueue. The Recipient List sends the request message to each individual bank queue, while the Aggregator selects the best quote from the reply messages arriving on the loanReplyQueue. Together, the Recipient List and the Aggregator act as a distribution-style Scatter-Gather. For simplicity's sake, all banks in this example use the same message format so that a Normalizer is not required. But because the common bank message format is different from the format expected by the consumer, we still need to use one Message Translator to convert bank reply messages into a loan broker reply message. I decided to design the loan broker as a Process Manager. Rather than implementing the functions inside the loan broker as individual components separated by message queues, the loan broker is a single component that executes all functions internally. This approach eliminates the overhead that would be incurred by sending messages across queues between these functions, but it requires the loan broker to maintain multiple, concurrent process instances.
具有消息队列接口的贷款经纪人
Loan Broker with Message Queue Interfaces
本节并不是对System.Messaging命名空间和 MSMQ的介绍。因此,将 MSMQ 特定的函数分成单独的类是有意义的,这样应用程序代码就不会充斥着 MSMQ 特定的命令。网关 [ EAA ]是用于此目的的绝佳模式,并提供两个关键优势:首先,它从应用程序中抽象了通信的技术细节。其次,如果我们选择将网关接口与网关实现分离,我们可以用Service Stub [ EAA ] 替换实际的外部服务进行测试。
This section is not meant as an introduction into the System.Messaging namespace and MSMQ. Therefore, it makes sense to separate the MSMQ-specific functions into separate classes so that the application code will not be littered with MSMQ-specific commands. Gateway [EAA] is an excellent pattern to use for this purpose and provides two key advantages: First, it abstracts the technical details of the communication from the application. Second, if we choose to separate the gateway interface from the gateway implementation, we can replace the actual external service with a Service Stub [EAA] for testing.
网关有助于将 MSMQ 详细信息排除在应用程序之外并提高可测试性
A Gateway Helps Keep MSMQ Details Out of the Application and Improves Testability
在我们的例子中,我们在消息网关中定义了两个接口: IMessageSender和IMessageReceiver 。我们让这些界面变得非常简单。IMessageSender能做的就是发送消息,而IMessageReceiver 能做的就是(惊讶!)接收消息。此外,接收方还有一个Begin方法来告诉它可以开始接收消息了。保持接口如此简单使得定义实现该接口的类变得容易。
In our case, we define two interfaces into the Messaging Gateway : IMessageSender and IMessageReceiver. We kept these interfaces almost trivially simplistic. All the IMessageSender can do is send a message, and all the IMessageReceiver can do is (surprise!) receive a message. Additionally, the receiver has a Begin method to tell it that it is okay to start receiving messages. Keeping the interfaces this simple makes it easy to define classes that implement the interface.
命名空间消息网关
{
使用系统消息传递;
公共接口 IMessageSender
{
void Send(消息混乱);
}
}
namespace MessageGateway
{
using System.Messaging;
public interface IMessageSender
{
void Send(Message mess);
}
}
命名空间消息网关
{
使用系统消息传递;
公共接口 IMessageReceiver
{
OnMsgEvent On消息
{
得到;
放;
}
无效开始();
消息队列 GetQueue();
}
}
namespace MessageGateway
{
using System.Messaging;
public interface IMessageReceiver
{
OnMsgEvent OnMessage
{
get;
set;
}
void Begin();
MessageQueue GetQueue();
}
}
实际的实现位于MessageSenderGateway 和MessageReceiverGateway类中。这些类负责配置消息队列属性,例如MessageReadPropertyFilter 或 Formatter设置。 MessageReceiverGateway 对MSMQ消息队列的ReceiveCompleted使用模板方法[GoF]来处理小而重要的细节,例如在处理。我们不会深入探讨这些功能的细节,而是建议您参考 MSDN 上的在线文档 [MSMQ ]。
The actual implementations reside in the MessageSenderGateway and MessageReceiverGateway classes. These classes take care of configuring the message queue properties, such as MessageReadPropertyFilter or Formatter settings. MessageReceiverGateway uses a Template Method [GoF] for the ReceiveCompleted event of the MSMQ message queue to take care of small but important details, such as calling the mq.BeginReceive method after processing the message. Instead of diving into the details of these features, we refer you to the online documentation on MSDN [MSMQ].
因为我们定义了非常狭窄的接口,所以也可以提供甚至不使用消息队列的实现。MockQueue实现了这两个接口,甚至没有引用消息队列!当应用程序发送消息时,MockQueue立即使用同一消息触发OnMessage 事件。这使得在单个地址空间中测试应用程序变得更加简单,而不必担心异步方面(更多关于测试的内容如下)。
Because we defined very narrow interfaces, it is also possible to provide an implementation that does not even use a message queue. MockQueue implements both interfaces without even referencing a message queue! When an application sends a message, MockQueue immediately triggers the OnMessage event with that same message. This makes testing the application in a single address space much simpler without having to worry about the asynchronous aspects (more on testing follows).
对于 C# 新手来说,IMessageReceiver中的 OnMsgEvent OnMessage行可能需要一些解释。.NET 框架为观察者模式[ GoF]提供了语言功能,称为委托和事件。 OnMsgEvent 是MessageReceiverGateway中定义的委托:
The line OnMsgEvent OnMessage in IMessageReceiver may require a little bit of explanation for those who are new to C#. The .NET framework provides language features for the Observer pattern [GoF], called delegates and events. OnMsgEvent is a delegate defined in the MessageReceiverGateway:
公共委托 void OnMsgEvent(Message msg);
public delegate void OnMsgEvent(Message msg);
委托允许对象注册某种类型的事件。当事件被调用时,.NET 调用所有已注册的方法。可以通过多种方式调用委托,但最简单的形式是使用委托名称直接调用:
A delegate allows objects to register with a certain type of event. When the event is invoked, .NET calls all registered methods. A delegate can be invoked in a number of ways, but the simplest form is the direct invocation by using the name of the delegate:
OnMsgEvent 接收器; 留言留言; ... 接收者(消息);
OnMsgEvent receiver; Message message; ... receiver(message);
如果这让您对委托更感兴趣,请看一本好的 .NET 或 C# 书籍。如果您想了解公共语言运行时 (CLR) 如何实现它们的详细细节,请查看 [ Box ]。
If this leaves you more interested in delegates, have a look at a good .NET or C# book. If you want to know the dirty details on how the Common Language Runtime (CLR) implements them, have a look at [Box].
当我们查看高层设计时,我们很快意识到贷款经纪人场景中的一些组件具有通用功能。例如,银行和征信机构都充当服务,接收请求、处理请求并将结果发布到另一个渠道。听起来很容易。但由于我们生活在异步消息传递的世界中,因此我们必须做一些额外的工作来实现即使是简单的请求-答复方案。首先,我们希望服务的调用者指定回复的返回地址。这允许不同的呼叫者使用相同的服务但使用不同的回复队列。该服务还应该支持相关标识符以便调用者可以协调传入的回复消息与请求消息。此外,如果服务接收到无法识别的格式的消息,最好将该消息路由到无效消息通道而不是简单地丢弃它。
When we look at the high-level design, we quickly realize that some of the components in the loan broker scenario have common functions. For example, both a bank and a credit bureau act as a service by receiving a request, processing the request, and publishing the result to another channel. Sounds easy enough. But since we live in the world of asynchronous messaging, we have to do a little extra work to implement even a simple request-reply scheme. First, we want the caller of the service to specify a Return Address for the reply. This allows different callers to use the same service but use different reply queues. The service should also support a Correlation Identifier so that the caller can reconcile incoming reply messages with request messages. Furthermore, if the service receives a message in an unrecognized format, it would be good manners to route the message to an Invalid Message Channel instead of simply discarding it.
为了消除代码重复(面向对象编程的致命罪过),我创建了基类MQService 。此类包含对返回地址和相关标识符的支持。实际上,服务器端对相关标识符的支持只不过是将传入消息的消息 ID 复制到回复消息的相关 ID。在我们的示例中,我们还复制了AppSpecific属性,因为稍后我们会看到,有时我们需要通过消息 ID 以外的属性进行关联。MQService还确保将响应发送到指定的返回地址。因为请求者提供Return Address ,所以MQService的唯一初始化参数是请求队列的名称(新请求消息进入的队列)。如果请求者忘记提供Return Address , RequestReplyService会将回复到无效消息通道。我们还可以考虑将请求消息发送到无效消息通道,因为这是导致故障的消息。现在,我们将保持简单,不涉及错误处理的细节。
To eliminate code duplication (the deadly sin of object-oriented programming), I create the base class MQService. This class incorporates support for Return Address and Correlation Identifier. Really, the server-side support for a Correlation Identifier consists of nothing more than copying the message ID of the incoming message to the correlation ID of the reply message. In our example, we also copy the AppSpecific property because we will see later that sometimes we need to correlate by a property other than the message ID. The MQService also makes sure to send the response to the specified Return Address. Because the requestor supplies the Return Address , the only initialization parameter for the MQService is the name of the request queuethe queue where new request messages come in. If the requestor forgets to supply a Return Address , the RequestReplyService sends the reply to the Invalid Message Channel. We may also consider sending the request message to the Invalid Message Channel because that's the message that caused the fault. For now, we will keep our lives simple and not get into the details of error handling.
公共抽象类 MQService
{
静态受保护只读字符串 InvalidMessageQueueName =
".\\private$\\invalidMessageQueue";
IMessageSender invalidQueue = new MessageSenderGateway(InvalidMessageQueueName);
受保护的 IMessageReceiver 请求队列;
受保护类型 requestBodyType;
公共MQService(IMessageReceiver接收器)
{
请求队列=接收者;
注册(请求队列);
}
公共 MQService(字符串请求队列名称)
{
MessageReceiverGateway q = new MessageReceiverGateway(requestQueueName,
获取格式化程序());
注册(q);
this.requestQueue = q;
Console.WriteLine("正在处理来自 " + requestQueueName 的消息);
}
受保护的虚拟 IMessageFormatter GetFormatter()
{
返回新的 XmlMessageFormatter(new Type[] { GetRequestBodyType() });
}
受保护的抽象类型 GetRequestBodyType();
受保护对象 GetTypedMessageBody(消息 msg)
{
尝试
{
if (msg.Body.GetType().Equals(GetRequestBodyType()))
{
返回消息正文;
}
别的
{
Console.WriteLine("消息格式非法。");
返回空值;
}
}
捕获(异常 e)
{
Console.WriteLine("消息格式非法" + e.Message);
返回空值;
}
}
公共无效注册(IMessageReceiver记录)
{
OnMsgEvent ev = new OnMsgEvent(OnMessage);
rec.OnMessage += ev;
}
公共无效运行()
{
requestQueue.Begin();
}
公共无效SendReply(对象outObj,消息inMsg)
{
消息 outMsg = 新消息(outObj);
outMsg.CorrelationId = inMsg.Id;
outMsg.AppSpecific = inMsg.AppSpecific;
if (inMsg.ResponseQueue != null)
{
IMessageSender 回复队列 = new MessageSenderGateway(inMsg.ResponseQueue);
回复队列.Send(outMsg);
}
别的
{
invalidQueue.Send(outMsg);
}
}
protected 摘要 void OnMessage(Message inMsg);
}
public abstract class MQService
{
static protected readonly String InvalidMessageQueueName =
".\\private$\\invalidMessageQueue";
IMessageSender invalidQueue = new MessageSenderGateway(InvalidMessageQueueName);
protected IMessageReceiver requestQueue;
protected Type requestBodyType;
public MQService(IMessageReceiver receiver)
{
requestQueue = receiver;
Register(requestQueue);
}
public MQService(String requestQueueName)
{
MessageReceiverGateway q = new MessageReceiverGateway(requestQueueName,
GetFormatter());
Register(q);
this.requestQueue = q;
Console.WriteLine("Processing messages from " + requestQueueName);
}
protected virtual IMessageFormatter GetFormatter()
{
return new XmlMessageFormatter(new Type[] { GetRequestBodyType() });
}
protected abstract Type GetRequestBodyType();
protected Object GetTypedMessageBody(Message msg)
{
try
{
if (msg.Body.GetType().Equals(GetRequestBodyType()))
{
return msg.Body;
}
else
{
Console.WriteLine("Illegal message format.");
return null;
}
}
catch (Exception e)
{
Console.WriteLine("Illegal message format" + e.Message);
return null;
}
}
public void Register(IMessageReceiver rec)
{
OnMsgEvent ev = new OnMsgEvent(OnMessage);
rec.OnMessage += ev;
}
public void Run()
{
requestQueue.Begin();
}
public void SendReply(Object outObj, Message inMsg)
{
Message outMsg = new Message(outObj);
outMsg.CorrelationId = inMsg.Id;
outMsg.AppSpecific = inMsg.AppSpecific;
if (inMsg.ResponseQueue != null)
{
IMessageSender replyQueue = new MessageSenderGateway(inMsg.ResponseQueue);
replyQueue.Send(outMsg);
}
else
{
invalidQueue.Send(outMsg);
}
}
protected abstract void OnMessage(Message inMsg);
}
该类是抽象的,因为它不提供GetTypedMessageBody 和OnMessage方法的实现。因为我们希望我们的类尽可能多地处理强类型业务对象而不是消息数据类型,所以我们让 MQService验证消息正文的类型并将其转换为正确的类型。问题是这个抽象基类不知道将其转换为哪种类型,因为该基类可以由许多不同的服务实现使用,每个服务实现都可能使用不同的消息类型。为了在基类中执行尽可能多的工作,我们创建了方法GetTypedMessageBody和抽象方法获取请求正文类型。每个子类都必须实现 GetRequestBodyType 方法来指定它期望接收的消息的类型。MQServer使用该类型来初始化 XML 格式化程序并执行类型检查。经过这些检查后,子类可以安全地将传入的消息正文转换为所需的类型,而不会导致异常。GetTypedMessageBody内部的异常处理在这一点上无疑是原始的,它所做的只是将消息打印到控制台。如果这不是一个简单的演示应用程序,我们肯定会使用更复杂的日志记录方法,或者更好的是,使用全面的控制总线。
The class is abstract because it does not provide an implementation for the GetTypedMessageBody and OnMessage methods. Because we want our classes to deal as much as possible with strongly typed business objects as opposed to Message data types, we have the MQService verify the type of the message body and cast it to the correct type. The problem is that this abstract base class does not know which type to cast it to because the base class can be used by many different service implementations, each of which is likely to use a different message type. To perform as much work as possible in the base class, we created the method GetTypedMessageBody and the abstract method GetRequestBodyType. Each subclass has to implement the method GetRequestBodyType to specify the type of the messages that it expects to receive. MQServer uses the type to initialize the XML formatter and to perform type checking. After these checks, the subclass can safely cast the incoming message body to the desired type without causing exceptions. The exception handling inside GetTypedMessageBody is admittedly primitive at this pointall it does is print a message to the console. If this weren't a simple demo app, we would definitely use a more sophisticated approach to logging or, better yet, a comprehensive Control Bus.
OnMessage方法由MQService 的子类来实现。我们提供两种实现,一种是同步的,一种是异步的。同步实现 ( RequestReplyService )调用虚拟方法ProcessMessage ,该方法预计返回回复消息,并立即调用 SendReply 。相反,异步实现 ( AsyncRequestReplyService ) 定义了没有任何返回参数的虚拟 ProcessMessage 方法。继承子类负责调用SendReply。
The OnMessage method is left to be implemented by the subclasses of MQService. We provide two implementations, a synchronous one and an asynchronous one. The synchronous implementation (RequestReplyService) calls the virtual method ProcessMessage, which is expected to return a reply message, and calls SendReply right away. The asynchronous implementation (AsyncRequestReplyService), in contrast, defines the virtual ProcessMessage method without any return parameter. The inheriting subclasses are responsible for calling SendReply.
公共类RequestReplyService:MQService
{
公共RequestReplyService(IMessageReceiver接收器):基(接收器){}
公共RequestReplyService(字符串请求队列名称):基(请求队列名称){}
受保护的覆盖类型 GetRequestBodyType()
{
返回 typeof(System.String);
}
受保护的虚拟对象 ProcessMessage(Object o)
{
字符串体 = (String)o;
Console.WriteLine("收到消息:" + body);
返回主体;
}
protected override void OnMessage(Message inMsg)
{
inMsg.Formatter = GetFormatter();
对象 inBody = GetTypedMessageBody(inMsg);
if (inBody!= null)
{
对象 outBody = ProcessMessage(inBody);
if (outBody!= null)
{
SendReply(outBody, inMsg);
}
}
}
}
公共类 AsyncRequestReplyService :MQService
{
公共 AsyncRequestReplyService(IMessageReceiver 接收器):基(接收器){}
公共 AsyncRequestReplyService(String requestQueueName) : base (requestQueueName) {}
受保护的覆盖类型 GetRequestBodyType()
{
返回 typeof(System.String);
}
protected virtual void ProcessMessage(Object o, Message msg)
{
字符串体 = (String)o;
Console.WriteLine("收到消息:" + body);
}
protected override void OnMessage(Message inMsg)
{
inMsg.Formatter = GetFormatter();
对象 inBody = GetTypedMessageBody(inMsg);
if (inBody!= null)
{
ProcessMessage(inBody, inMsg);
}
}
}
public class RequestReplyService : MQService
{
public RequestReplyService(IMessageReceiver receiver) : base(receiver) {}
public RequestReplyService(String requestQueueName) : base (requestQueueName) {}
protected override Type GetRequestBodyType()
{
return typeof(System.String);
}
protected virtual Object ProcessMessage(Object o)
{
String body = (String)o;
Console.WriteLine("Received Message: " + body);
return body;
}
protected override void OnMessage(Message inMsg)
{
inMsg.Formatter = GetFormatter();
Object inBody = GetTypedMessageBody(inMsg);
if (inBody != null)
{
Object outBody = ProcessMessage(inBody);
if (outBody != null)
{
SendReply(outBody, inMsg);
}
}
}
}
public class AsyncRequestReplyService : MQService
{
public AsyncRequestReplyService(IMessageReceiver receiver) : base(receiver) {}
public AsyncRequestReplyService(String requestQueueName) : base (requestQueueName) {}
protected override Type GetRequestBodyType()
{
return typeof(System.String);
}
protected virtual void ProcessMessage(Object o, Message msg)
{
String body = (String)o;
Console.WriteLine("Received Message: " + body);
}
protected override void OnMessage(Message inMsg)
{
inMsg.Formatter = GetFormatter();
Object inBody = GetTypedMessageBody(inMsg);
if (inBody != null)
{
ProcessMessage(inBody, inMsg);
}
}
}
这两个类都提供GetRequestBodyType 和ProcessMessage方法的默认实现。GetRequestBodyType指定消息需要一个简单的字符串, ProcessMessage 将该字符串到控制台。从技术上讲,我们可以从RequestReplyService 和AsyncRequestReplyService类中省略这些方法的默认实现以便它们保持抽象。这将允许编译器检测到这些类之一的任何子类忘记实现抽象方法之一。然而,最好有一个可用于测试和调试目的的服务的默认实现,因此我们让这些类具体化,以便它们可以按原样实例化。
Both classes provide a default implementation of the GetRequestBodyType and ProcessMessage methods. GetRequestBodyType specifies that the message expects a simple string, and ProcessMessage prints that string to the console. Technically speaking, we could have omitted the default implementations of these methods from the classes RequestReplyService and AsyncRequestReplyService so that they remain abstract. This would allow the compiler to detect any subclass of one of these classes that forgot to implement one of the abstract methods. However, it is nice to have a default implementation of a service available for testing and debugging purposes, so we let these classes be concrete so that they can be instantiated as is.
总之,基类的类图如下所示(我们将很快讨论银行、信用局和贷款经纪人类):
In summary, the class diagram for the base classes looks as follows (we discuss the bank, credit bureau, and loan broker classes shortly):
消息服务的基类
Base Classes for Message Services
现在我们已经创建了一组基类和实用函数,是时候开始实现应用程序逻辑了。开始创建解决方案的一种简单方法是按依赖关系的相反顺序构建应用程序组件。这意味着,我们首先创建不依赖于其他任何东西的组件。这使我们能够独立运行和测试这些组件。银行无疑是其中之一。贷款经纪人依赖于银行,但银行本身是独立的。很方便的是,银行是请求-回复服务的一个主要示例,因此实现银行应该像继承RequestReplyService 并填充一些业务逻辑一样简单。
Now that we have created a set of base classes and utility functions, it is time to start implementing the application logic. An easy way to start creating the solution is to build the application components by reverse order of dependency. This means, we first create components that do not depend on anything else. This allows us to run and test these components independently. The bank is certainly one of those components. The loan broker depends on the banks, but the banks themselves are self-contained. Conveniently enough, a bank is a prime example of a request-reply-service, so implementing a bank should be as simple as inheriting from RequestReplyService and filling in some business logic.
不过,在开始研究银行的内部结构之前,我们应该定义外部接口。我们需要定义贷款报价请求和回复的消息类型。为了我们的简单实现,我们为所有银行定义了一个通用消息格式,这样我们就可以为所有五个银行实例使用一个通用类。C# 支持结构体,因此我们使用它们作为消息类型:
Before we start working on the internals of the bank, though, we should define the external interface. We need to define the message types for loan quote requests and replies. For our simple implementation, we define a common message format for all banks so we can use a common class for all five bank instances. C# supports structs, so we use those as message types:
公共结构 BankQuoteRequest
{
公共 int SSN;
公共 int 信用评分;
公共 int 历史长度;
公共 int 贷款金额;
公共 int LoanTerm;
}
公共结构 BankQuoteReply
{
公共双倍利率;
公共字符串报价ID;
公共 int 错误代码;
}
public struct BankQuoteRequest
{
public int SSN;
public int CreditScore;
public int HistoryLength;
public int LoanAmount;
public int LoanTerm;
}
public struct BankQuoteReply
{
public double InterestRate;
public String QuoteID;
public int ErrorCode;
}
因为我们希望对所有银行实例使用单个类,所以我们需要针对不同的行为对银行进行参数化。我们的银行是非常简单的机构,因此唯一的参数是BankName 、 RatePremium和MaxLoanTerm 。RatePremium决定了银行收取高于优惠利率的利率点数,基本上就是银行的利润率。MaxLoanTerm指定银行愿意延长的最长贷款期限(以月为单位)。如果贷款请求的期限比规定的时间长,值得庆幸的是,银行会拒绝。插入适当的便捷构造函数和访问器后,我们可以构建ProcessMessage银行方法:
Because we want to use a single class for all bank instances, we need to parameterize the banks for different behavior. Our banks are very simple institutions, so the only parameters are BankName, RatePremium, and MaxLoanTerm. The RatePremium determines the number of interest rate points that the bank charges above the prime ratebasically, the bank's profit margin. The MaxLoanTerm specifies the longest loan term (in months) that the bank is willing to extend. If a loan request is for a longer duration than specified, the bank will thankfully decline. After plugging in the appropriate convenience constructors and accessors, we can build the ProcessMessage method of the bank:
内部类 Bank : RequestReplyService
{
...
受保护的覆盖类型 GetRequestBodyType()
{
返回类型(BankQuoteRequest);
}
受保护的 BankQuoteReply ComputeBankReply(BankQuoteRequest requestStruct)
{
BankQuoteReplyreplyStruct = new BankQuoteReply();
if (requestStruct.LoanTerm <= MaxLoanTerm)
{
replyStruct.InterestRate = PrimeRate + RatePremium
+ (双)(requestStruct.LoanTerm / 12)/10
+ (双)随机.Next(10) / 10;
回复结构.ErrorCode = 0;
}
别的
{
回复结构.InterestRate = 0.0;
回复结构.ErrorCode = 1;
}
replyStruct.QuoteID = String.Format("{0}-{1:00000}", BankName, quoteCounter);
报价计数器++;
返回回复结构体;
}
受保护的覆盖对象 ProcessMessage(Object o)
{
BankQuoteRequest requestStruct;
BankQuoteReply回复结构;
requestStruct = (BankQuoteRequest)o;
回复结构 = ComputeBankReply(requestStruct);
Console.WriteLine("收到了 {1:c}/{2} 个月的 SSN {0} 请求",
requestStruct.SSN、requestStruct.LoanAmount、
requestStruct.LoanTerm);
Thread.Sleep(随机.Next(10) * 100);
Console.WriteLine("报价:{0} {1} {2}",
回复结构.ErrorCode, 回复结构.InterestRate,
回复结构.QuoteID);
返回回复结构体;
}
}
internal class Bank : RequestReplyService
{
...
protected override Type GetRequestBodyType()
{
return typeof(BankQuoteRequest);
}
protected BankQuoteReply ComputeBankReply(BankQuoteRequest requestStruct)
{
BankQuoteReply replyStruct = new BankQuoteReply();
if (requestStruct.LoanTerm <= MaxLoanTerm)
{
replyStruct.InterestRate = PrimeRate + RatePremium
+ (double)(requestStruct.LoanTerm / 12)/10
+ (double)random.Next(10) / 10;
replyStruct.ErrorCode = 0;
}
else
{
replyStruct.InterestRate = 0.0;
replyStruct.ErrorCode = 1;
}
replyStruct.QuoteID = String.Format("{0}-{1:00000}", BankName, quoteCounter);
quoteCounter++;
return replyStruct;
}
protected override Object ProcessMessage(Object o)
{
BankQuoteRequest requestStruct;
BankQuoteReply replyStruct;
requestStruct = (BankQuoteRequest)o;
replyStruct = ComputeBankReply(requestStruct);
Console.WriteLine("Received request for SSN {0} for {1:c} / {2} months",
requestStruct.SSN, requestStruct.LoanAmount,
requestStruct.LoanTerm);
Thread.Sleep(random.Next(10) * 100);
Console.WriteLine(" Quote: {0} {1} {2}",
replyStruct.ErrorCode, replyStruct.InterestRate,
replyStruct.QuoteID);
return replyStruct;
}
}
我们可以看到,具体服务只需实现GetRequestBodyType 和ProcessMessage方法。该服务可以安全地转换 ProcessMessage 传入的对象,因为基类已经验证了正确的类型。正如我们所看到的,其余的实现与消息传递几乎没有关系,所有细节都在基类中处理。MQService和RequestReplyService 类充当服务激活器,使应用程序不必深入研究消息传递系统的详细信息。
We can see that the concrete service has to implement only the GetRequestBodyType and ProcessMessage methods. The service can safely cast the object passed in by ProcessMessage because the base class has already verified the correct type. As we can see, the remaining implementation has rather little to do with messagingall the details are taken care of in the base classes. The MQService and RequestReplyService classes act as a Service Activator , keeping the application from having to dig into messaging system details.
ComputeBankReply方法包含银行的完整业务逻辑。生活要是这么简单就好了!嗯,这不是宏观经济学的介绍,而是消息传递的示例,因此我们采取了一些自由来简化事情。计算的利率是最优惠利率、配置的利率溢价、贷款期限和一些随机性的总和。如果请求的贷款期限长于银行可接受的期限,则会返回错误代码。银行发出的每个报价都会收到一个唯一的报价 ID,以便客户稍后可以参考。在当前的实现中,一个简单的递增计数器创建这些 ID。
The method ComputeBankReply contains the complete business logic for a bank. If life were only so simple! Well, this is not an introduction to macroeconomics, but an example of messaging, so we took some liberties to simplify things. The computed interest rate is the sum of the prime rate, the configured rate premium, the loan term, and a sprinkle of randomness. If the requested loan term is longer than the bank is comfortable with, it returns an error code. Each quote that the bank issues receives a unique quote ID so the customer may refer back to it later. In the current implementation, a simple incrementing counter creates these IDs.
ProcessMessage方法包含一个小延迟(1/10 到 1 秒之间),使银行交易更加真实。ProcessMessage还将一些活动记录到控制台,以便我们可以看到当我们在简单的控制台应用程序中运行它时发生了什么。
The ProcessMessage method incorporates a small delay (between 1/10 and 1 second) to make the bank transaction a bit more realistic. The ProcessMessage also logs some activities to the console so we can see what is going on when we run it inside a simple console application.
要启动银行,首先我们使用适当的参数实例化它,然后调用它从 MQService 继承的Run方法。 由于处理是通过事件进行的,因此Run方法会立即返回。因此,我们一定要注意不要在程序启动后就终止它。对于我们的简单测试,我只需在调用Run之后插入Console.ReadLine()语句。
To start a bank, first we instantiate it with the appropriate parameters, and then we call the Run method that it inherits from MQService. Since the processing happens through events, the Run method returns right away. Therefore, we must be careful not to terminate the program right after it starts. For our simple tests, I simply insert a Console.ReadLine() statement after the call to Run.
信用局的实施类似于银行。唯一的区别在于消息类型和业务逻辑。信用局可以处理以下消息类型:
The credit bureau implementation is analogous to the bank. The only difference is in the message types and the business logic. The credit bureau can handle the following message types:
公共课 CreditBureauRequest
{
公共 int SSN;
}
公开课 CreditBureauReply
{
公共 int SSN;
公共 int 信用评分;
公共 int 历史长度;
}
public class CreditBureauRequest
{
public int SSN;
}
public class CreditBureauReply
{
public int SSN;
public int CreditScore;
public int HistoryLength;
}
ProcessMessage方法与银行代码几乎相同,只是它处理不同的数据结构并调用不同的应用程序逻辑。信用局也有一个内置的延迟。
The ProcessMessage method is nearly identical to the bank code, except it deals with different data structures and invokes different application logic. The credit bureau also has a built-in delay.
私有 int getCreditScore(int ssn)
{
return (int)(随机.Next(600) + 300);
}
私有 int getCreditHistoryLength(int ssn)
{
return (int)(随机.Next(19) + 1);
}
private int getCreditScore(int ssn)
{
return (int)(random.Next(600) + 300);
}
private int getCreditHistoryLength(int ssn)
{
return (int)(random.Next(19) + 1);
}
现在我们有了一个正常运作的信用局和一个银行类,可以让我们实例化银行的多个化身,我们准备好进行贷款经纪人的内部设计。本书中的路由和转换模式帮助我们细分贷款经纪人需要提供的功能。我们可以将贷款经纪人的内部功能分为三个主要部分(见图):接受客户请求的请求-答复接口、征信局接口和银行接口。
Now that we have a functioning credit bureau and a bank class that lets us instantiate multiple incarnations of a bank, we are ready to work in the internal design of the loan broker. The routing and transformation patterns from this book help us segment the functions that the loan broker needs to provide. We can group the internal functions of the loan broker into three main portions (see figure): the request-reply interface that accepts requests from clients, the credit bureau interface, and the bank interface.
贷款经纪人的内部结构
Internal Structure of the Loan Broker
与以相反的依赖顺序构建整个解决方案类似,让我们开始构建仅依赖于已有内容的部分。因为我们刚刚构建了一家银行和一个信用局服务,所以创建从贷款经纪人到这些外部组件的接口是有意义的。信用局界面看起来确实更简单,所以让我们从这里开始。
In a similar fashion to building the whole solution in reverse order of dependency, let's start building the pieces that depend only on what's already there. Because we just built a bank and a credit bureau service, it makes sense to create the interface from the loan broker to these external components. The credit bureau interface definitely seems simpler, so let's start there.
贷款经纪人需要向信用局提出请求,以获得银行要求的客户信用评级。这意味着向外部贷款经纪人组件发送消息并接收回复消息。将发送通用消息的详细信息封装在 MessageGateway 中,使我们能够向应用程序的其余部分隐藏许多 MSMQ 详细信息。遵循相同的推理,我们应该将向信用局发送和接收消息封装在信用局网关内。该信用局网关执行语义丰富的重要功能,允许贷款经纪人调用 GetCreditScore 等方法,而不是调用之类的方法。发送消息。这使得贷款经纪人代码更具可读性,并为贷款经纪人和征信局之间的通信提供了强有力的封装。下图说明了通过“链接”两个网关实现的抽象级别。
The loan broker needs to make requests to the credit bureau to obtain the customer's credit rating, which is required by the bank. This implies sending a message to the external loan broker component and receiving reply messages. Wrapping the details of sending a generic message inside a MessageGateway allowed us to hide many MSMQ details from the rest of the application. Following the same reasoning, we should encapsulate sending and receiving messages to the credit bureau inside a credit bureau gateway. This credit bureau gateway performs the important function of semantic enrichment, allowing the loan broker to call methods such as GetCreditScore as opposed to SendMessage. This makes the loan broker code more readable and provides a strong encapsulation of the communication between the loan broker and the credit bureau. The following diagram illustrates the levels of abstraction achieved by "chaining" the two gateways.
贷款经纪人为消息传递基础设施提供了额外的抽象级别
The Loan Broker Provides an Additional Level of Abstraction from the Messaging Infrastructure
为了请求信用评分,网关需要创建CreditBureauRequest 结构的实例,如信用局指定的。同样,该接口将在CreditBureauReply内接收结果结构。我们之前说过,该解决方案是由单独的可执行文件构建的,以便信用局可以在与贷款经纪人不同的计算机上运行。但这意味着贷款经纪人可能无法访问信用局程序集中定义的类型,而且实际上,我们不希望贷款经纪人对信用局内部进行任何引用,因为这会消除松散的好处通过消息队列进行耦合。贷款经纪人应该完全不知道信用评分请求哪些组件服务。然而,贷款经纪人需要访问定义消息格式的结构。幸运的是,Microsoft .NET Framework SDK 包含一个工具可以让我们做到这一点,即 XML 架构定义工具 ( xsd.exe)。该工具可以从程序集创建 XML 架构,还可以从 XML 架构创建 C# 源代码。下图描述了该过程:
In order to request a credit score, the gateway needs to create an instance of a CreditBureauRequest struct, as specified by credit bureau. Likewise, the interface will receive the results inside a CreditBureauReply struct. We stated earlier that the solution is built from separate executables so that the credit bureau can run on a different computer than the loan broker. This means, though, that the loan broker may not have access to types defined in the credit bureau's assembly, and really, we would not want the loan broker to make any references to the credit bureau internals, because that would eliminate the benefits of loose coupling over message queues. The loan broker is supposed to be completely unaware of what component services the credit score requests. The loan broker needs, however, access to the structs that define the message formats. Luckily, the Microsoft .NET Framework SDK contains a tool that lets us do just that, the XML Schema Definition Tool (xsd.exe). This tool can create XML schemas from an assembly and also create C# source code from XML schemas. The following figure describes the process:
从另一个程序集创建类存根
Creating Class Stubs from Another Assembly
xsd.exe提取公共类型定义并根据类型定义和控制序列化的可选属性创建 XML 架构文件。在我们的例子中,xsd.exe创建了以下架构:
xsd.exe extracts public type definitions and creates an XML schema file based on the type definition and optional attributes that control serialization. In our case, xsd.exe created the following schema:
<?xml 版本=“1.0”编码=“utf-8”?>
<xs:schema elementFormDefault="合格" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="CreditBureauRequest" type="CreditBureauRequest" />
<xs:complexType name="CreditBureauRequest">
<xs:序列>
<xs:element minOccurs="1" maxOccurs="1" name="SSN" type="xs:int" />
</xs:序列>
</xs:复杂类型>
<xs:element name="CreditBureauReply" type="CreditBureauReply" />
<xs:complexType name="CreditBureauReply">
<xs:序列>
<xs:element minOccurs="1" maxOccurs="1" name="SSN" type="xs:int" />
<xs:element minOccurs="1" maxOccurs="1" name="CreditScore" type="xs:int" />
<xs:element minOccurs="1" maxOccurs="1" name="HistoryLength" type="xs:int" />
</xs:序列>
</xs:复杂类型>
<xs:element name="Run" nillable="true" type="Run" />
<xs:complexType name="运行"/>
</xs:架构>
<?xml version="1.0" encoding="utf-8"?>
<xs:schema elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="CreditBureauRequest" type="CreditBureauRequest" />
<xs:complexType name="CreditBureauRequest">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="SSN" type="xs:int" />
</xs:sequence>
</xs:complexType>
<xs:element name="CreditBureauReply" type="CreditBureauReply" />
<xs:complexType name="CreditBureauReply">
<xs:sequence>
<xs:element minOccurs="1" maxOccurs="1" name="SSN" type="xs:int" />
<xs:element minOccurs="1" maxOccurs="1" name="CreditScore" type="xs:int" />
<xs:element minOccurs="1" maxOccurs="1" name="HistoryLength" type="xs:int" />
</xs:sequence>
</xs:complexType>
<xs:element name="Run" nillable="true" type="Run" />
<xs:complexType name="Run" />
</xs:schema>
通常,服务会将此架构定义发布给潜在的调用者。这使得调用者可以选择以多种不同的方式生成所需的消息格式。首先,调用者可以显式构造符合 XSD 的请求消息。或者,调用者可以使用 .NET 内置序列化。在这种情况下,客户端仍然可以选择不同的编程语言,因为 .NET CLR 是独立于编程语言的。
Usually, a service would publish this schema definition to potential callers. This allows the caller the option to produce the required message format in a number of different ways. First, the caller could construct the XSD-compliant request message explicitly. Alternatively, the caller could use the .NET built-in serialization. In this case, the client would still have a choice of different programming languages since the .NET CLR is programming languageindependent.
我们决定使用.NET 的内置序列化。因此,我们再次运行xsd.exe来创建供服务使用者使用的源文件,我们得到一个如下所示的文件:
We decide to use .NET's built-in serialization. Therefore, we run xsd.exe again to create source files to be used by the service consumer, and we get a file that looks like this:
//
// 该源代码由 xsd 自动生成,版本=1.1.4322.573。
//
命名空间 CreditBureau {
使用 System.Xml.Serialization;
/// <备注/>
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]
公共类 CreditBureauRequest {
/// <备注/>
公共 int SSN;
}
...
}
//
// This source code was auto-generated by xsd, Version=1.1.4322.573.
//
namespace CreditBureau {
using System.Xml.Serialization;
/// <remarks/>
[System.Xml.Serialization.XmlRootAttribute(Namespace="", IsNullable=false)]
public class CreditBureauRequest {
/// <remarks/>
public int SSN;
}
...
}
值得注意的是.NET XML 序列化和反序列化允许松散耦合。因此,从技术上讲,我们发送给征信局的请求消息不必与征信局实现内部使用的 CLR 类型完全相同,只要 XML 表示形式包含所需的元素即可。例如,这将允许请求者发送 XML 表示包含附加元素的消息,而不会干扰通信。对于我们的示例,我们假设贷款经纪人愿意遵守信用局指定的格式并在通信两端使用相同的数据类型。
It is worth noting that the .NET XML serialization and deserialization allows loose coupling. So, technically speaking, the request message that we send to the credit bureau does not have to be of the exact same CLR type that is used inside the credit bureau implementation as long as the XML representation contains the required elements. For example, this would allow a requestor to send a message whose XML representation contains additional elements without disturbing the communication. For our example, we assume that the loan broker is willing to conform to the credit bureau's specified format and use the same data types on both ends of the communication.
我们现在准备好以正确的格式向信用局发送消息。但我们需要记住,这种通信是异步的,具有单独的异步请求消息和回复消息。我们可以设计信用局网关,以便在发送请求后,网关代码等待直到响应返回。这种方法有一个显着的缺点:当信用局处理消息时,应用程序只会等待。这种类型的伪同步处理很快就会导致性能瓶颈。如果我们使流程的每一步都伪同步,则意味着贷款经纪人一次只能处理一个请求流程。例如,当它仍在等待银行对先前报价请求的答复时,它将无法请求新请求的信用评分。为了直观地看出差异,我们假设贷款经纪人必须执行两个主要步骤:获取信用评分并从银行获得最佳报价。如果我们假设贷款经纪人仅运行单个顺序执行,则执行将如下图上半部分所示:
We are now ready to send messages in the correct format to the credit bureau. We need to keep in mind, though, that this communication is asynchronous with a separate, asynchronous request message and reply message. We could design the credit bureau gateway so that after sending a request, the gateway code waits until the response comes back. This approach has one significant drawback: The application will just sit and wait while the credit bureau is processing a message. This type of pseudo-synchronous processing can quickly result in a performance bottleneck. If we make each step of the process pseudo-synchronous, it means that the loan broker can process only one request process at a time. For example, it would not be able to request the credit score for a new request while it is still waiting for bank replies for the previous quote request. To visualize the difference, let's consider that the loan broker has to perform two main steps: Get the credit score and get the best quote from the banks. If we assume the loan broker runs only a single, sequential execution, the execution will look like the top half of the following figure:
管道处理可以提供显着更高的吞吐量
Pipeline Processing Can Provide Significantly Higher Throughput
由于大部分实际工作是由外部组件执行的,因此贷款经纪人组件基本上只是等待结果,而不是有效利用计算资源。如果我们将整个贷款经纪人流程设计为事件驱动的消费者,我们就可以开始并行处理多个请求,并在结果传入时对其进行处理。我们将此模式称为管道加工。系统的可扩展性现在仅取决于外部组件的处理能力,而不取决于贷款经纪人。如果我们仅运行信用局进程的单个实例,则差异可能不会那么明显,因为信用局请求队列无论如何都会对请求进行排队。但是,如果我们决定并行运行多个信用局实例,我们将看到立即的性能提升(更多关于性能的信息)。
Because the bulk of the actual work is executed by external components, the loan broker component basically sits around and waits for resultsnot an efficient use of computing resources. If we design the whole loan broker process as an Event-Driven Consumer, we can start processing multiple requests in parallel and process the results as they come in. We call this mode pipeline processing. The scalability of the system now depends only on the processing capacity of the external components and not on the loan broker. If we run only a single instance of the credit bureau process, the difference may not be as pronounced because the bureau request queue will queue up the requests anyway. However, if we decide to run multiple credit bureau instances in parallel, we will see immediate performance gains (more on performance to follow).
有两种主要方法可以使贷款经纪人流程成为事件驱动的。我们可以创建一个顺序进程,但为每个传入的请求消息创建一个新线程。或者,我们可以让消息系统在事件待处理时通知贷款经纪人。这样,我们就让消息系统控制执行线程。每种方法都有优点和缺点。编写顺序过程可以使代码更容易理解;但是,如果我们的组件主要是在外部实体之间代理消息的代理组件,则可能会导致大量线程除了等待传入消息外什么都不做。这些线程可能会消耗大量系统资源,但完成的任务却很少。所以,让消息系统驱动执行可能会更好。每当消息准备好被使用时,系统就会调用代理执行。这让我们可以维护单个执行线程,而不必担心线程管理。然而,我们需要处理这样一个事实:执行路径不是单个顺序方法,而是随着消息到达而执行的多个代码段。
There are two primary ways to make the loan broker process event-driven. We can create a sequential process but create a new thread for each incoming request message. Alternatively, we can let the messaging system notify the loan broker whenever an event is pending. This way, we let the messaging system control the threads of execution. Each approach has pros and cons. Coding a sequential process can make the code easier to understand; however, if our component is primarily a broker component that brokers messages between external entities, it would result in a potentially large number of threads that are doing nothing much but waiting for incoming messages. These threads could consume a large number of system resources and accomplish little. Therefore, we may be better off letting the messaging system drive the execution. Whenever a message is ready to be consumed, the system will invoke the broker execution. This lets us maintain a single thread of execution and not worry about thread management. However, we need to deal with the fact that the execution path is not a single sequential method, but multiple code segments that are executed as messages arrive.
您可能已经猜到,在 .NET 中使事情成为事件驱动的方法是使用委托。正如预期的那样,信用局网关定义了一个新的委托。
You might have guessed that the way to make things event-driven in .NET is to use delegates. As expected, the credit bureau gateway defines a new delegate.
public delegate void OnCreditReplyEvent(CreditBureauReply CreditReply, Object ACT);
public delegate void OnCreditReplyEvent(CreditBureauReply creditReply, Object ACT);
该委托允许其他代码段告诉信用局网关在结果传入时调用哪个方法。信用局网关将正确类型的CreditBureauReply 结构传递回调用者。它还传递我们称为 ACTan异步完成令牌[ POSA2 ] 的东西。此令牌允许调用者将数据传递到网关,并在相应的回复消息传入时接收返回的数据(请参阅消息传递网关)。基本上,信用局网关对请求和回复消息执行关联,这样调用者就不必这样做。
This delegate allows other code segments to tell the credit bureau gateway which method to call when the result comes in. The credit bureau gateway passes a properly typed CreditBureauReply struct back to the caller. It also passes something we call ACTan Asynchronous Completion Token [POSA2]. This token allows the caller to pass in data to the gateway and receive the data back when the corresponding reply message comes in (see Messaging Gateway). Basically, the credit bureau gateway performs correlation for the request and reply messages so that the caller does not have to.
剩下的是请求信用评分的方法和处理传入回复消息的方法,关联正确的 ACT 并调用正确类型的委托。
What's left are a method to request a credit score and a method that handles an incoming reply message, correlating the proper ACT and invoking the properly typed delegate.
内部结构 CreditRequestProcess
{
公共 int CorrelationID;
公共对象 ACT;
公共 OnCreditReplyEvent 回调;
}
内部类 CreditBureauGateway
{
受保护的 IMessageSender 信用请求队列;
受保护的 IMessageReceiver 信用回复队列;
受保护的 IDictionary activeProcesses = (IDictionary)(new Hashtable());
受保护的随机随机 = new Random();
公共无效监听()
{
CreditReplyQueue.Begin();
}
公共无效GetCreditScore(CreditBureauRequest quoteRequest,
OnCreditReplyEvent OnCreditResponse,对象 ACT)
{
消息 requestMessage = new Message(quoteRequest);
requestMessage.ResponseQueue = CreditReplyQueue.GetQueue();
requestMessage.AppSpecific = random.Next();
CreditRequestProcess processInstance = new CreditRequestProcess();
processInstance.ACT = ACT;
processInstance.callback = OnCreditResponse;
processInstance.CorrelationID = requestMessage.AppSpecific;
CreditRequestQueue.Send(requestMessage);
activeProcesses.Add(processInstance.CorrelationID, processInstance);
}
私人无效OnCreditResponse(消息msg)
{
msg.Formatter = GetFormatter();
CreditBureau回复replyStruct;
尝试
{
if (msg.Body 是 CreditBureauReply)
{
replyStruct = (CreditBureauReply)msg.Body;
int CorrelationID = msg.AppSpecific;
if (activeProcesses.Contains(CorrelationID))
{
CreditRequestProcess processInstance =
(CreditRequestProcess)(activeProcesses[CorrelationID]);
processInstance.callback(replyStruct, processInstance.ACT);
activeProcesses.Remove(CorrelationID);
}
else { Console.WriteLine
(“传入的信用响应与任何请求都不匹配”);}
}
别的
{ Console.WriteLine("非法回复。"); }
}
捕获(异常 e)
{
Console.WriteLine("异常:{0}", e.ToString());
}
}
}
internal struct CreditRequestProcess
{
public int CorrelationID;
public Object ACT;
public OnCreditReplyEvent callback;
}
internal class CreditBureauGateway
{
protected IMessageSender creditRequestQueue;
protected IMessageReceiver creditReplyQueue;
protected IDictionary activeProcesses = (IDictionary)(new Hashtable());
protected Random random = new Random();
public void Listen()
{
creditReplyQueue.Begin();
}
public void GetCreditScore(CreditBureauRequest quoteRequest,
OnCreditReplyEvent OnCreditResponse, Object ACT)
{
Message requestMessage = new Message(quoteRequest);
requestMessage.ResponseQueue = creditReplyQueue.GetQueue();
requestMessage.AppSpecific = random.Next();
CreditRequestProcess processInstance = new CreditRequestProcess();
processInstance.ACT = ACT;
processInstance.callback = OnCreditResponse;
processInstance.CorrelationID = requestMessage.AppSpecific;
creditRequestQueue.Send(requestMessage);
activeProcesses.Add(processInstance.CorrelationID, processInstance);
}
private void OnCreditResponse(Message msg)
{
msg.Formatter = GetFormatter();
CreditBureauReply replyStruct;
try
{
if (msg.Body is CreditBureauReply)
{
replyStruct = (CreditBureauReply)msg.Body;
int CorrelationID = msg.AppSpecific;
if (activeProcesses.Contains(CorrelationID))
{
CreditRequestProcess processInstance =
(CreditRequestProcess)(activeProcesses[CorrelationID]);
processInstance.callback(replyStruct, processInstance.ACT);
activeProcesses.Remove(CorrelationID);
}
else { Console.WriteLine
("Incoming credit response does not match any request"); }
}
else
{ Console.WriteLine("Illegal reply."); }
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}", e.ToString());
}
}
}
当调用者通过 GetCreditScore 方法请求信用评分时,信用局网关会分配CreditRequestProcess 结构的新实例。activeProcesses集合包含每个未完成实例,由消息的相关标识符作为键控。该结构还保存OnCreditReplyEvent事件的委托。存储每条消息的委托允许每个调用者为每个请求指定不同的回调位置。正如我们稍后将看到的,这允许调用者使用委托来管理对话状态。
When a caller requests a credit score via the GetCreditScoremethod, the credit bureau gateway allocates a new instance of the CreditRequestProcess structure. The collection activeProcesses contains one instance of CreditRequestProcess for each outstanding request, keyed by the Correlation Identifier of the message. The structure also holds the delegate for the OnCreditReplyEvent event. Storing the delegate for each message allows each caller to specify a different callback location for each request. As we will see later, this allows the caller to use delegates to manage conversation state.
需要注意的是,我们不使用消息的内置消息 ID 字段进行关联。相反,我们为AppSpecific字段分配一个随机整数,并通过该字段的值关联传入消息(请记住,我们设计RequestReplyService 来复制ID 字段和AppSpecific回复消息字段)。为什么我们想要通过消息 ID 以外的其他内容进行关联?消息ID的优点是对于系统中的每条消息来说它是唯一的。但这也限制了我们的灵活性。要求回复消息与请求消息的消息 ID 相关联不允许我们在消息流中插入中间步骤(例如路由器)。由于任何中间步骤都会使用请求消息并向服务发布新消息,因此回复消息的CorrelationId 将与服务收到的消息匹配,但与贷款经纪人最初发送的消息不匹配(见图)。
It is important to note that we do not use the message's built-in message ID field to correlate. Instead, we assign a random integer number to the AppSpecific field and correlate incoming messages by the value of that field (remember that we designed the RequestReplyService to copy both the ID field and the AppSpecific field to the reply message). Why would we want to correlate by something other than the message ID? The advantage of the message ID is that it is unique for each message in the system. But that also limits our flexibility. Requiring a reply message to correlate to the message ID of the request message does not allow us to insert intermediate steps (for example, a router) into the message flow. Because any intermediate step would consume the request message and publish a new message to the service, the reply message's CorrelationId would match the message the service received but not the message that the loan broker originally sent (see figure).
中介阻碍了与系统生成的消息 ID 的关联
Intermediaries Hinder Correlation with System-Generated Message IDs
这个问题有两种解决方案。首先,任何中间体都需要拦截请求和回复消息,并为回复消息配备正确的CorrelationId 值(有关此方法的示例,请参阅智能代理 [xxx] ) 。或者,我们可以使用单独的字段来实现关联目的,以便流经中介和服务的所有相关消息都携带相同的关联标识符。在这个例子中,我们选择了第二种方法,使中介组件更容易拦截贷款经纪人和信用局之间的请求消息(我们将在第 12 章中利用这一点),“插曲:系统管理示例。”)我们应该如何为AppSpecific属性选择一个值?我们可以使用顺序值,但是我们必须小心两个并发实例不要使用相同的起始值。我们还可以使用中央 ID 生成模块(例如数据库)来保证系统范围内的唯一性。对于这个简单的例子来说,这似乎有点麻烦,所以我们选择了一个随机数。.NET 将随机数生成为带符号的 32 位整数,因此重复的几率是我们愿意承担的二亿分之一的风险。
There are two solutions to this problem. First, any intermediate would be required to intercept both request and reply messages and to equip reply messages with the correct CorrelationId value (for an example of this approach, see the Smart Proxy [xxx]). Alternatively, we can use a separate field for correlation purposes so that all related messages that flow through the intermediary and the service carry the same Correlation Identifier. In this example, we chose the second approach to make it easier for an intermediary component to intercept request messages between the loan broker and the credit bureau (we will take advantage of this in Chapter 12, "Interlude: Systems Management Example.") How should we pick a value for the AppSpecific property? We could use sequential values, but then we have to be careful that two concurrent instances do not use the same starting value. We could also use a central ID generation module (e.g., a database) that guarantees systemwide uniqueness. This seemed a little too much trouble for this simple example, so we chose a random number. .NET generates random numbers as signed 32-bit integers so that the odds of a duplicate are 1 in 2 billiona risk we are willing to take.
信用局网关现在提供了我们所期望的 Windows 消息队列基础结构的清晰抽象。信用局网关的唯一公共接口(除了构造函数之外)是委托和两个方法:
The credit bureau gateway now provides the clean abstraction from the Windows message queuing infrastructure that we were aiming for. The only public interface into the credit bureau gateway (besides constructors) is a delegate and two methods:
delegate void OnCreditReplyEvent(CreditBureauReply CreditReply, Object ACT);
CreditBureauGateway 类 {
无效监听(){...}
void GetCreditScore(CreditBureauRequest quoteRequest,
OnCreditReplyEvent OnCreditResponse,
对象 ACT) {...}
...
}
delegate void OnCreditReplyEvent(CreditBureauReply creditReply, Object ACT);
class CreditBureauGateway {
void Listen() {...}
void GetCreditScore(CreditBureauRequest quoteRequest,
OnCreditReplyEvent OnCreditResponse,
Object ACT) {...}
...
}
这两个构造都不引用消息或消息队列。这提供了许多好处。首先,我们可以轻松实现完全不依赖消息队列的信用局网关的存根版本(类似于 MockQueue )。其次,如果我们决定使用不同于 MSMQ 的传输方式,我们可以替换信用局网关的实现。例如,如果我们要使用使用 SOAP 和 HTTP 的 Web 服务接口而不是 MSMQ,则网关公开的方法很可能根本不需要更改。
Neither construct makes any reference to a message or a message queue. This provides a number of benefits. First, we can easily implement a stubbed out version of the credit bureau gateway that does not rely on message queues at all (similar to the MockQueue). Second, we can replace the implementation of the credit bureau gateway if we decide to use a transport different from MSMQ. For example, if we were going to use a Web services interface using SOAP and HTTP instead of MSMQ, the methods exposed by the gateway would most likely not have to change at all.
银行网关的设计遵循与信用局网关设计相同的原则。我们使用与之前相同的过程来声明银行指定的请求和回复消息类型的存根。银行网关的外部部分与信用局网关非常相似:
The design of the bank gateway follows the same principles as the design of the credit bureau gateway. We use the same process as before to declare stubs for the request and reply message types specified by the bank. The external portion of the bank gateway is very similar to the credit bureau gateway:
delegate void OnBestQuoteEvent(BankQuoteReply bestQuote, Object ACT);
类银行网关{
无效监听(){...}
void GetBestQuote(BankQuoteRequest quoteRequest,
OnBestQuoteEvent onBestQuoteEvent,
对象 ACT) {...}
...
}
delegate void OnBestQuoteEvent(BankQuoteReply bestQuote, Object ACT);
class BankGateway {
void Listen() {...}
void GetBestQuote(BankQuoteRequest quoteRequest,
OnBestQuoteEvent onBestQuoteEvent,
Object ACT) {...}
...
}
内部工作稍微复杂一些,因为Scatter-Gather交互方式将单个BankQuoteRequest 路由到多个银行。同样,单个BankQuoteReply通常是多个银行报价回复消息的结果。前一部分由收件人列表处理,而后者由聚合器处理。让我们从收件人列表开始。它必须实现三个主要功能:
The internal workings are slightly more complex because the Scatter-Gather style of interaction routes a single BankQuoteRequest to multiple banks. Likewise, a single BankQuoteReply is usually the result of multiple bank quote reply messages. The former part is handled by a Recipient List , while the latter is handled by an Aggregator. Let's start with the Recipient List. It has to implement three main functions:
计算适当的接收者
Computation of appropriate recipients
将消息发送给收件人
Sending the message to the recipients
初始化聚合器以处理传入的回复
Initializing the Aggregator to process incoming replies
正如本章介绍中所述,此实现使用Scatter -Gather 的分发方式,主动确定将请求路由到哪些银行。如果银行向经纪人收取每次报价的费用,或者如果银行和经纪人达成协议,要求经纪人对其产生的潜在客户进行资格预审,则这种方法具有商业意义。贷款经纪人根据客户的信用评分、贷款金额和信用记录的长度做出路由决策。我们将与银行的每个连接封装在一个继承自抽象类BankConnection 的类中。此类包含对正确寻址的消息队列的引用和方法CanHandleLoanRequest确定是否应将报价请求转发给该银行。BankConnectionManager只是迭代所有银行连接的列表,并编译符合贷款报价标准的列表。如果银行列表更长,我们可以考虑实施可配置的规则引擎。我们更喜欢当前的方法,因为它简单且明确。
As described in the introduction of this chapter, this implementation uses the distribution style of Scatter-Gather , actively determining which banks to route the request to. This approach makes business sense if the banks charge the broker for each quote or if the bank and the broker have an agreement that requires the broker to prequalify leads he or she generates. The loan broker makes the routing decision based on the customer's credit score, the amount of the loan and the length of the credit history. We encapsulate each connection to a bank inside a class that inherits from the abstract class BankConnection. This class contains a reference to the properly addressed message queue and a method CanHandleLoanRequest that determines whether the quote request should be forwarded to this bank. The BankConnectionManager simply iterates through the list of all bank connections and compiles a list of those that match the criteria of the loan quote. If the list of banks was longer, we could consider implementing a configurable rules engine. We prefer the current approach because it is simple and explicit.
内部类 BankConnectionManager
{
静态受保护的 BankConnection[] 银行 =
{新银行1(),新银行2(),新银行3(),新银行4(),新银行5()};
公共 IMessageSender[] GetEligibleBankQueues
(int CreditScore、int HistoryLength、int LoanAmount)
{
ArrayList 贷方 = new ArrayList();
for (int 索引 = 0; 索引 < 银行.长度; 索引++)
{
if (银行[索引].CanHandleLoanRequest(CreditScore, HistoryLength,
贷款额度))
贷方.Add(银行[index].Queue);
}
IMessageSender[] lenderArray = (IMessageSender [])Array.CreateInstance
(typeof(IMessageSender), 贷方.Count);
贷方.CopyTo(lenderArray);
返回贷方数组;
}
}
内部抽象类 BankConnection
{
受保护的 MessageSenderGateway 队列;
protected String 银行名称 = "";
公共 MessageSenderGateway 队列
{
获取{返回队列; }
}
公共字符串银行名称
{
获取 { 返回银行名称; }
}
public BankConnection(MessageQueue队列)
{ this.queue = new MessageSenderGateway(queue); }
公共银行连接(字符串队列名称)
{ this.queue = new MessageSenderGateway(queueName); }
公共抽象 bool CanHandleLoanRequest(int CreditScore, int HistoryLength,
int 贷款金额);
}
内部类 Bank1 : BankConnection
{
protected Stringbankname = "独家乡村俱乐部银行家";
public Bank1 () : base (".\\private$\\bank1Queue") {}
公共覆盖 bool CanHandleLoanRequest(int CreditScore, int HistoryLength,
int 贷款金额)
{
return LoanAmount >= 75000 && CreditScore >= 600 && HistoryLength >= 8;
}
}
...
internal class BankConnectionManager
{
static protected BankConnection[] banks =
{new Bank1(), new Bank2(), new Bank3(), new Bank4(), new Bank5() };
public IMessageSender[] GetEligibleBankQueues
(int CreditScore, int HistoryLength, int LoanAmount)
{
ArrayList lenders = new ArrayList();
for (int index = 0; index < banks.Length; index++)
{
if (banks[index].CanHandleLoanRequest(CreditScore, HistoryLength,
LoanAmount))
lenders.Add(banks[index].Queue);
}
IMessageSender[] lenderArray = (IMessageSender [])Array.CreateInstance
(typeof(IMessageSender), lenders.Count);
lenders.CopyTo(lenderArray);
return lenderArray;
}
}
internal abstract class BankConnection
{
protected MessageSenderGateway queue;
protected String bankName = "";
public MessageSenderGateway Queue
{
get { return queue; }
}
public String BankName
{
get { return bankName; }
}
public BankConnection (MessageQueue queue)
{ this.queue = new MessageSenderGateway(queue); }
public BankConnection (String queueName)
{ this.queue = new MessageSenderGateway(queueName); }
public abstract bool CanHandleLoanRequest(int CreditScore, int HistoryLength,
int LoanAmount);
}
internal class Bank1 : BankConnection
{
protected String bankname = "Exclusive Country Club Bankers";
public Bank1 () : base (".\\private$\\bank1Queue") {}
public override bool CanHandleLoanRequest(int CreditScore, int HistoryLength,
int LoanAmount)
{
return LoanAmount >= 75000 && CreditScore >= 600 && HistoryLength >= 8;
}
}
...
一旦编制了相关银行的列表,发送消息就很简单了,只需迭代该列表即可。在生产应用程序中,此迭代应在单个事务内进行,以避免出现错误情况,即消息可能会发送到某些银行,但不会发送到其他银行。我们再次选择让这个例子简单化。
Once the list of relevant banks is compiled, sending the message is a simple matter of iterating over the list. In a production application, this iteration should occur inside a single transaction to avoid error conditions where a message may be sent to some banks but not to others. Once again, we chose to let simplicity prevail for this example.
内部类 MessageRouter
{
公共静态无效SendToRecipientList(消息msg,IMessageSender []收件人列表)
{
IEnumerator e =收件人列表.GetEnumerator();
while (e.MoveNext())
{
((IMessageSender)e.Current).Send(msg);
}
}
}
internal class MessageRouter
{
public static void SendToRecipientList (Message msg, IMessageSender[] recipientList)
{
IEnumerator e = recipientList.GetEnumerator();
while (e.MoveNext())
{
((IMessageSender)e.Current).Send(msg);
}
}
}
现在请求消息已发送至银行,我们需要初始化聚合器以接收传入的银行报价。由于贷款经纪人的事件驱动性质,聚合器需要准备好处理多个聚合,同时为每个待处理的报价请求维护一个活动聚合。这意味着传入消息需要与特定聚合唯一相关。不幸的是,我们不能使用消息 ID 作为关联标识符,因为收件人列表需要向每个银行发送单独的消息。因此,如果三个银行参与报价请求,则接收者列表需要发送三条独特的消息,每条消息发送给每家银行。这些消息中的每条消息都将具有唯一的消息 ID,因此,如果银行要通过消息 ID 进行关联,则三个响应将具有不同的关联 ID,即使它们属于同一聚合。这将使聚合器无法识别相关消息。我们可以让聚合存储每个请求消息的消息 ID,从而将传入消息的相关 ID 关联回聚合。然而,这比我们需要的更复杂。相反,我们只是为每个聚合生成我们自己的相关 IDsone,而不是为每条消息生成一个相关 IDsone。我们将此(数字)ID 存储在AppSpecific中传出请求消息的属性。银行继承自RequestReplyService ,它已经将传入消息的AppSpecific 属性传输到回复消息。当报价消息从银行传入时,BankGateway 可以通过消息的AppSpecific(见图)。
Now that request messages are on their way to the banks, we need to initialize the Aggregator to expect incoming bank quotes. Due to the event-driven nature of the loan broker, the aggregator needs to be prepared to work on more than one aggregate concurrentlymaintaining one active aggregate for each quote request that is pending. This means that incoming messages need to be uniquely correlated to a specific aggregate. Unfortunately, we cannot use the message ID as the correlation identifier because the Recipient List needs to send individual messages to each of the banks. As a result, if three banks participate in a quote request, the Recipient List needs to send three unique messages, one to each bank. Each of these messages will have a unique message ID, so if the banks were to correlate by message ID, the three responses will have different correlation IDs even though they belong to the same aggregate. This would make it impossible for the aggregator to identify related messages. We could have the aggregate store the message ID for each request message and thus correlate the incoming message's correlation ID back to the aggregate. However, this is more complicated than we need it to be. Instead, we just generate our own correlation IDsone for each aggregate as opposed to one for each message. We store this (numeric) ID in the AppSpecific property of the outgoing request messages. The banks inherit from RequestReplyService, which already transfers the incoming message's AppSpecific property to the reply messages. When a quote message comes in from a bank, the BankGateway can easily correlate the incoming message by the AppSpecific property of the message (see figure).
BankGateway 使用 AppSpecific 消息属性将响应消息与聚合相关联
The BankGateway Uses the AppSpecific Message Property to Correlate Response Messages to Aggregates
银行网关使用聚合 ID(由简单计数器生成)和预期消息数来初始化聚合。此外,调用者需要提供委托,并且可以选择指定对 ACT 的对象引用,就像信用局网关的工作方式一样。聚合策略很简单。当所有选定的银行都回复了回复消息时,汇总即被视为完成。收件人名单使用预期消息的数量初始化聚合。请记住,银行可以选择拒绝提供报价。为了让我们知道聚合何时完成,如果银行不想提供报价,我们要求银行提供包含错误代码集的回复消息。我们可以很容易地修改聚合策略,例如,在 1 秒后停止竞价,并采取迄今为止最好的响应。
The bank gateway initializes an aggregate with the aggregate ID (generated by a simple counter) and the number of expected messages. In addition, the caller needs to supply a delegate and can optionally specify an object reference to an ACT in the same way the credit bureau gateway functioned. The aggregation strategy is simple. The aggregate is considered complete when all selected banks have responded with a reply message. The Recipient List initializes the aggregate with the number of expected messages. Remember that banks have the option of declining to provide a quote. So that we can know when an aggregate is complete, we require the banks to provide a reply message with the error code set if they do not want to provide a quote. We could easily modify the aggregation strategy, for example, to cut off the bidding after 1 second and take the best response up to that point.
内部类 BankQuoteAggregate
{
受保护的 int ID;
受保护的int预期消息;
受保护对象 ACT;
受保护的 OnBestQuoteEvent 回调;
受保护的双倍最佳率 = 0.0;
protected ArrayList receiveMessages = new ArrayList();
受保护的 BankQuoteReply bestReply = null;
公共 BankQuoteAggregate(int ID, int ExpectedMessages, OnBestQuoteEvent 回调,
对象行动)
{
这个.ID = ID;
this.expectedMessages = ExpectedMessages;
this.callback = 回调;
这个.ACT = ACT;
}
公共无效AddMessage(BankQuoteReply回复)
{
if (回复.ErrorCode == 0)
{
if (bestReply == null)
{
最佳回复=回复;
}
别的
{
if (reply.InterestRate < bestReply.InterestRate)
{
最佳回复=回复;
}
}
}
receiveMessages.Add(回复);
}
公共 bool IsComplete()
{
返回 receiveMessages.Count == ExpectedMessages;
}
公共 BankQuoteReply getBestResult()
{
返回最佳回复;
}
公共无效NotifyBestResult()
{
if(回调!= null)
{
回调(bestReply,ACT);
}
}
}
internal class BankQuoteAggregate
{
protected int ID;
protected int expectedMessages;
protected Object ACT;
protected OnBestQuoteEvent callback;
protected double bestRate = 0.0;
protected ArrayList receivedMessages = new ArrayList();
protected BankQuoteReply bestReply = null;
public BankQuoteAggregate(int ID, int expectedMessages, OnBestQuoteEvent callback,
Object ACT)
{
this.ID = ID;
this.expectedMessages = expectedMessages;
this.callback = callback;
this.ACT = ACT;
}
public void AddMessage(BankQuoteReply reply)
{
if (reply.ErrorCode == 0)
{
if (bestReply == null)
{
bestReply = reply;
}
else
{
if (reply.InterestRate < bestReply.InterestRate)
{
bestReply = reply;
}
}
}
receivedMessages.Add(reply);
}
public bool IsComplete()
{
return receivedMessages.Count == expectedMessages;
}
public BankQuoteReply getBestResult()
{
return bestReply;
}
public void NotifyBestResult()
{
if (callback != null)
{
callback(bestReply, ACT);
}
}
}
有了银行连接管理器、收件人列表和聚合, BankGateway 的功能实现就变得相对简单:
Armed with the bank connection manager, the recipient list, and the aggregate, the implementation of the BankGateway's functions becomes relatively simple:
内部类BankGateway
{
受保护的 IMessageReceiver 银行回复队列;
受保护的 BankConnectionManager 连接管理器;
受保护的 IDictionaryaggregateBuffer = (IDictionary)(new Hashtable());
受保护的 int 聚合CorrelationID;
公共无效监听()
{
银行ReplyQueue.Begin();
}
公共无效GetBestQuote(BankQuoteRequest quoteRequest,
OnBestQuoteEvent onBestQuoteEvent,对象 ACT)
{
消息 requestMessage = new Message(quoteRequest);
requestMessage.AppSpecific = AggregationCorrelationID;
requestMessage.ResponseQueue =bankReplyQueue.GetQueue();
IMessageSender[] 合格银行 =
connectionManager.GetEligibleBankQueues(quoteRequest.CreditScore,
quoteRequest.HistoryLength,
quoteRequest.LoanAmount);
aggregateBuffer.Add(aggregationCorrelationID,
新的 BankQuoteAggregate(aggregationCorrelationID, 有资格的Banks.Length,
onBestQuoteEvent,ACT));
聚合CorrelationID++;
MessageRouter.SendToRecipientList(requestMessage,合格银行);
}
私人无效OnBankMessage(消息msg)
{
msg.Formatter = GetFormatter();
BankQuoteReply回复结构;
尝试
{
if (msg.Body 是 BankQuoteReply)
{
回复结构 = (BankQuoteReply)msg.Body;
int AggregationCorrelationID = msg.AppSpecific;
Console.WriteLine("引用 {0:0.00}% {1} {2}",
回复结构.InterestRate, 回复结构.QuoteID,
回复结构.ErrorCode);
if (aggregateBuffer.Contains(aggregationCorrelationID))
{
BankQuoteAggregate 合计 =
(BankQuoteAggregate)(aggregateBuffer[aggregationCorrelationID]);
聚合.AddMessage(replyStruct);
if (聚合.IsComplete())
{
聚合.NotifyBestResult();
aggregateBuffer.Remove(aggregationCorrelationID);
}
}
别的
{ Console.WriteLine("收款银行响应与任何
总计的”); }
}
别的
{ Console.WriteLine("非法请求。"); }
}
捕获(异常 e)
{
Console.WriteLine("异常:{0}", e.ToString());
}
}
}
internal class BankGateway
{
protected IMessageReceiver bankReplyQueue;
protected BankConnectionManager connectionManager;
protected IDictionary aggregateBuffer = (IDictionary)(new Hashtable());
protected int aggregationCorrelationID;
public void Listen()
{
bankReplyQueue.Begin();
}
public void GetBestQuote(BankQuoteRequest quoteRequest,
OnBestQuoteEvent onBestQuoteEvent, Object ACT)
{
Message requestMessage = new Message(quoteRequest);
requestMessage.AppSpecific = aggregationCorrelationID;
requestMessage.ResponseQueue = bankReplyQueue.GetQueue();
IMessageSender[] eligibleBanks =
connectionManager.GetEligibleBankQueues(quoteRequest.CreditScore,
quoteRequest.HistoryLength,
quoteRequest.LoanAmount);
aggregateBuffer.Add(aggregationCorrelationID,
new BankQuoteAggregate(aggregationCorrelationID, eligibleBanks.Length,
onBestQuoteEvent, ACT));
aggregationCorrelationID++;
MessageRouter.SendToRecipientList(requestMessage, eligibleBanks);
}
private void OnBankMessage(Message msg)
{
msg.Formatter = GetFormatter();
BankQuoteReply replyStruct;
try
{
if (msg.Body is BankQuoteReply)
{
replyStruct = (BankQuoteReply)msg.Body;
int aggregationCorrelationID = msg.AppSpecific;
Console.WriteLine("Quote {0:0.00}% {1} {2}",
replyStruct.InterestRate, replyStruct.QuoteID,
replyStruct.ErrorCode);
if (aggregateBuffer.Contains(aggregationCorrelationID))
{
BankQuoteAggregate aggregate =
(BankQuoteAggregate)(aggregateBuffer[aggregationCorrelationID]);
aggregate.AddMessage(replyStruct);
if (aggregate.IsComplete())
{
aggregate.NotifyBestResult();
aggregateBuffer.Remove(aggregationCorrelationID);
}
}
else
{ Console.WriteLine("Incoming bank response does not match any
aggregate"); }
}
else
{ Console.WriteLine("Illegal request."); }
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}", e.ToString());
}
}
}
当银行网关收到银行的报价回复时,OnBankMessage方法就会执行。该方法将传入消息转换为正确的类型,并继续通过AppSpecific属性查找相关聚合。它将新的出价添加到总计中。一旦聚合完成(如BankQuoteAggregate类中所定义), BankGateway 将调用调用者提供的委托。
When the bank gateway receives a quote reply from a bank, the OnBankMessage method executes. The method converts the incoming message to the correct type and goes on to locate the related aggregate via the AppSpecific property. It adds the new bid to the aggregate. Once the aggregate is complete (as defined in the BankQuoteAggregate class), the BankGateway invokes the delegate supplied by the caller.
现在我们有了封装良好的信用局网关和银行网关,我们已准备好让贷款经纪人接受请求。前面,我们讨论了MQService和AsyncRequestReplyService 基类的设计。LoanBroker类继承自AsyncRequestReplyService,因为它不能立即将结果发送回回复队列,而只能在其他几个异步操作(获取信用存储并与银行通信)完成后才将结果发送回回复队列。
Now that we have a well-encapsulated credit bureau gateway and bank gateway, we are ready to have the loan broker accept requests. Earlier, we discussed the design of the MQService and AsyncRequestReplyService base classes. The LoanBroker class inherits from AsyncRequestReplyService because it cannot send the results back to the reply queue right away, but only after several other asynchronous operations (obtaining the credit store and communicating with the banks) complete.
实现 LoanBroker 的第一步是定义贷款经纪人处理的消息类型:
The first step in implementing the LoanBroker is to define the message types the loan broker handles:
公共结构 LoanQuoteRequest
{
公共 int SSN;
公共双倍贷款金额;
公共 int LoanTerm;
}
公共结构 LoanQuoteReply
{
公共 int SSN;
公共双倍贷款金额;
公共双倍利率;
公共字符串QuoteID;
}
public struct LoanQuoteRequest
{
public int SSN;
public double LoanAmount;
public int LoanTerm;
}
public struct LoanQuoteReply
{
public int SSN;
public double LoanAmount;
public double InterestRate;
public string QuoteID;
}
接下来,我们创建一个继承自AsyncRequestReplyService 的类并重写ProcessMessage 方法。
Next, we create a class that inherits from AsyncRequestReplyService and override the ProcessMessage method.
贷款经纪人与前面的类不同,因为传入消息触发的过程不包含在任何单个方法中。相反,该过程的完成取决于一系列外部事件。贷款经纪人可以接收三种类型的事件:
The loan broker is different from the previous classes because the process that is triggered by an incoming message is not contained in any single method. Instead, the completion of the process depends on a sequence of external events. The loan broker can receive three types of events:
收到新的贷款请求消息。
A new loan request message arrives.
信用评分回复消息到达(通过 CreditBureauGateway ) 。
A credit score reply message arrives (via the CreditBureauGateway).
银行报价消息到达(通过 BankGateway ) 。
A bank quote message arrives (via the BankGateway).
由于贷款经纪人的逻辑分布在多个事件处理程序中,因此我们需要在这些函数中保持经纪人的状态。这就是异步完成令牌的用武之地!请记住,信用局网关和银行网关允许调用者(贷款经纪人)在发送请求时传递对对象实例的引用。当网关收到回复消息时,将对象引用传回。为了利用此功能,我们在贷款经纪人中声明 ACT,如下所示:
Since the logic for the loan broker is spread across multiple event handlers, we need to keep the state of the broker across these functions. That's where the asynchronous completion tokens come in! Remember that the credit bureau gateway and the bank gateway allow the caller (the loan broker) to pass a reference to an object instance when sending a request. The gateway passes the object reference back when the reply message is received. To take advantage of this functionality, we declare an ACT in the loan broker as follows:
内部类ACT
{
公共贷款报价请求贷款请求;
公共留言留言;
公共ACT(LoanQuoteRequest贷款请求,消息消息)
{
this.loanRequest = 贷款请求;
this.message = 消息;
}
}
internal class ACT
{
public LoanQuoteRequest loanRequest;
public Message message;
public ACT(LoanQuoteRequest loanRequest, Message message)
{
this.loanRequest = loanRequest;
this.message = message;
}
}
ACT 包含原始请求消息的副本(其中包含创建回复消息所需的消息 ID 和回复地址)和请求数据结构(需要将 SSN 和贷款金额复制到回复消息中)。从技术上讲,ACT存储了少量的重复信息,因为我们可以从请求消息中提取请求结构的内容。然而,访问强类型结构的便利性值得花费一些额外的字节。
The ACT contains a copy of the original request message (which contains the message ID and the reply address required to create the reply message) and the request data structure (needed to copy the SSN and the loan amount into the reply message). Technically speaking, the ACT stores a small amount of duplicate information because we could extract the content of the request structure from the request message. However, the convenience of accessing a strongly typed structure is worth the few extra bytes.
贷款经纪人的其余部分实施如下:
The remainder of the loan broker is implemented as follows:
内部类 LoanBroker :AsyncRequestReplyService
{
受保护的 ICreditBureauGateway 信用局接口;
受保护的银行网关银行接口;
公共贷款经纪人(字符串请求队列名称,
字符串creditRequestQueueName,字符串creditReplyQueueName,
字符串银行回复队列名称,
BankConnectionManager 连接管理器):base(requestQueueName)
{
CreditBureauInterface = (ICCreditBureauGateway)
(new CreditBureauGatewayImp(creditRequestQueueName, CreditReplyQueueName));
CreditBureauInterface.Listen();
BankInterface = new BankGateway(bankReplyQueueName, 连接管理器);
银行接口.Listen();
}
受保护的覆盖类型 GetRequestBodyType()
{
返回类型(LoanQuoteRequest);
}
protected override void ProcessMessage(对象o,消息msg)
{
贷款报价请求报价请求;
quoteRequest = (LoanQuoteRequest)o;
CreditBureauRequest 信用请求 =
LoanBrokerTranslator.GetCreditBureaurequest(quoteRequest);
ACT act = new ACT(quoteRequest, msg);
CreditBureauInterface.GetCreditScore(creditRequest,
新的 OnCreditReplyEvent(OnCreditReply), act);
}
private void OnCreditReply(CreditBureauReply CreditReply, Object act)
{
ACT myAct = (ACT) 行动;
Console.WriteLine("收到的信用评分 -- SSN {0} 评分 {1} 长度 {2}",
CreditReply.SSN、creditReply.CreditScore、
CreditReply.HistoryLength);
银行报价请求 银行请求 =
LoanBrokerTranslator.GetBankQuoteRequest(myAct.loanRequest ,creditReply);
BankInterface.GetBestQuote(bankRequest, new OnBestQuoteEvent(OnBestQuote), act);
}
private void OnBestQuote(BankQuoteReply bestQuote, Object act)
{
ACT myAct = (ACT) 行动;
LoanQuoteReply quoteReply = LoanBrokerTranslator.GetLoanQuoteReply
(myAct.loanRequest, bestQuote);
Console.WriteLine("最佳报价{0} {1}",
quoteReply.InterestRate, quoteReply.QuoteID);
SendReply(quoteReply, myAct.message);
}
}
internal class LoanBroker : AsyncRequestReplyService
{
protected ICreditBureauGateway creditBureauInterface;
protected BankGateway bankInterface;
public LoanBroker(String requestQueueName,
String creditRequestQueueName, String creditReplyQueueName,
String bankReplyQueueName,
BankConnectionManager connectionManager): base(requestQueueName)
{
creditBureauInterface = (ICreditBureauGateway)
(new CreditBureauGatewayImp(creditRequestQueueName, creditReplyQueueName));
creditBureauInterface.Listen();
bankInterface = new BankGateway(bankReplyQueueName, connectionManager);
bankInterface.Listen();
}
protected override Type GetRequestBodyType()
{
return typeof(LoanQuoteRequest);
}
protected override void ProcessMessage(Object o, Message msg)
{
LoanQuoteRequest quoteRequest;
quoteRequest = (LoanQuoteRequest)o;
CreditBureauRequest creditRequest =
LoanBrokerTranslator.GetCreditBureaurequest(quoteRequest);
ACT act = new ACT(quoteRequest, msg);
creditBureauInterface.GetCreditScore(creditRequest,
new OnCreditReplyEvent(OnCreditReply), act);
}
private void OnCreditReply(CreditBureauReply creditReply, Object act)
{
ACT myAct = (ACT)act;
Console.WriteLine("Received Credit Score -- SSN {0} Score {1} Length {2}",
creditReply.SSN, creditReply.CreditScore,
creditReply.HistoryLength);
BankQuoteRequest bankRequest =
LoanBrokerTranslator.GetBankQuoteRequest(myAct.loanRequest ,creditReply);
bankInterface.GetBestQuote(bankRequest, new OnBestQuoteEvent(OnBestQuote), act);
}
private void OnBestQuote(BankQuoteReply bestQuote, Object act)
{
ACT myAct = (ACT)act;
LoanQuoteReply quoteReply = LoanBrokerTranslator.GetLoanQuoteReply
(myAct.loanRequest, bestQuote);
Console.WriteLine("Best quote {0} {1}",
quoteReply.InterestRate, quoteReply.QuoteID);
SendReply(quoteReply, myAct.message);
}
}
LoanBroker继承自AsyncRequestReplyService ,它提供对接收请求和发送相关回复的支持。LoanBroker重写ProcessMessage 方法来处理传入的请求消息。ProcessMessage创建ACT 的新实例并调用信用局网关来请求信用评分。有趣的是,该方法到此结束。当信用局网关调用OnCreditReply (由ProcessMessage时,处理继续方法。此方法使用 ACT 和信用局回复来创建银行报价请求,并调用银行网关发送请求消息。这次它指定方法OnBestQuote作为回调委托。一旦银行网关收到所有银行报价回复,它就会通过委托调用此方法并传回 ACT 实例。OnBestQuote 使用银行报价和 ACT 创建对客户的回复,并使用SendReply。
LoanBroker inherits from AsyncRequestReplyService, which provides support for receiving requests and sending correlated replies. LoanBroker overrides the method ProcessMessage to deal with incoming request messages. ProcessMessage creates a new instance of the ACT and calls the credit bureau gateway to request a credit score. Interestingly, the method ends there. Processing continues when the credit bureau gateway invokes OnCreditReply, the delegate specified by the ProcessMessage method. This method uses the ACT and the credit bureau reply to create a bank quote request and calls the bank gateway to send the request messages. This time it specifies the method OnBestQuote as the callback delegate. Once the bank gateway receives all bank quote replies, it invokes this method via the delegate and passes back the instance of the ACT. OnBestQuote uses the bank quote and the ACT to create a reply to the customer and sends it off using the base class implementation of SendReply.
您可能在源代码中注意到的一类是LoanBrokerTranslator 。此类提供了一些静态方法,有助于在不同消息格式之间进行转换。
One class you probably noticed in the source code is the LoanBrokerTranslator. This class provides a handful of static methods that help convert between the different message formats.
LoanBroker类演示了我们在设计中所做的权衡。该代码没有引用消息传递或线程相关的概念(除了从AsyncRequestReplyService 继承),这使得代码非常易于阅读。然而,主函数的执行分布在三个方法中,除了委托之外,这些方法之间没有直接引用。如果不考虑整体解决方案(包括所有外部组件),这可能会使执行流程难以理解。
The LoanBroker class demonstrates the trade-off we made in our design. The code is free of references to messaging or thread-related concepts (except for the inheritance from AsyncRequestReplyService), which makes the code very easy to read. However, the execution of the main function is spread across three methods that make no direct reference to each other besides the delegates. This can make the flow of execution hard to understand without considering the total solution, including all external components.
当我们观察贷款经纪人的运作方式时,我们意识到我们正在分离数据和功能。我们有一个LoanBroker类的实例,它通过 ACT 集合模拟多个实例。虽然 ACT 非常有用,但它们似乎违背了面向对象编程的精神,因为它们将构成对象的数据和功能这两个部分分开。但是,如果我们以更好的方式使用委托,我们可以重构LoanBroker类以避免重复查找 ACT。委托本质上是类型安全的函数指针。因此,它们指向特定的对象实例。因此,不要向信用局和银行网关提供对唯一LoanBroker中方法的引用例如,我们可以使用委托来指向“流程对象”的特定实例,该实例维护当前状态,就像 ACT 所做的那样,但也包含贷款经纪人流程的逻辑。为此,我们将 ACT 转换为一个名为LoanBrokerProcess 的新类,并将消息处理函数移至该类中:
When we look at the way the loan broker functions, we realize that we are separating data and functionality. We have one instance of the LoanBroker class that emulates multiple instances by means of the ACT collection. While ACTs are very useful, they seem to go against the spirit of object-oriented programming by separating data and functionalitythe two parts that make up an object. However, we can refactor the LoanBroker class to avoid the repeated lookup of the ACT if we use the delegates in a better way. Delegates are essentially type-safe function pointers. As such, they point to a specific object instance. So, rather than supplying the credit bureau and bank gateway with a reference to a method in the sole LoanBroker instance, we can use the delegates to point to a specific instance of a "process object" that maintains the current state, as an ACT does, but also contains the logic of the loan broker process. To do this, we turn the ACT into a new class called LoanBrokerProcess and move the message handler functions into this class:
内部类 LoanBrokerProcess
{
受保护的 LoanBrokerPM 经纪人;
受保护的字符串进程ID;
受保护的 LoanQuoteRequest 贷款请求;
受保护的Message消息;
受保护的 CreditBureauGateway 信用局网关;
受保护的银行网关银行接口;
公共LoanBrokerProcess(LoanBrokerPM经纪人,字符串processID,
CreditBureauGateway 信用局网关,
银行网关银行网关,
LoanQuoteRequest 贷款请求、消息 msg)
{
this.broker = 经纪人;
this.creditBureauGateway = 经纪人.CreditBureauGateway;
this.bankInterface = 经纪人.BankInterface;
this.processID = processID;
this.loanRequest = 贷款请求;
这个.message = msg;
CreditBureauRequest 信用请求 =
LoanBrokerTranslator.GetCreditBureaurequest(loanRequest);
CreditBureauGateway.GetCreditScore(creditRequest,
新的 OnCreditReplyEvent(OnCreditReply), null);
}
private void OnCreditReply(CreditBureauReply CreditReply, Object act)
{
Console.WriteLine("收到的信用评分 -- SSN {0} 评分 {1} 长度 {2}",
CreditReply.SSN、creditReply.CreditScore、creditReply.HistoryLength);
银行报价请求 银行请求 =
LoanBrokerTranslator.GetBankQuoteRequest(loanRequest, CreditReply);
BankInterface.GetBestQuote(bankRequest, new OnBestQuoteEvent(OnBestQuote), null);
}
private void OnBestQuote(BankQuoteReply bestQuote, Object act)
{
LoanQuoteReply quoteReply = LoanBrokerTranslator.GetLoanQuoteReply
(贷款请求,最佳报价);
Console.WriteLine("最佳报价{0} {1}",
quoteReply.InterestRate, quoteReply.QuoteID);
Broker.SendReply(quoteReply, 消息);
Broker.OnProcessComplete(processID);
}
}
internal class LoanBrokerProcess
{
protected LoanBrokerPM broker;
protected String processID;
protected LoanQuoteRequest loanRequest;
protected Message message;
protected CreditBureauGateway creditBureauGateway;
protected BankGateway bankInterface;
public LoanBrokerProcess(LoanBrokerPM broker, String processID,
CreditBureauGateway creditBureauGateway,
BankGateway bankGateway,
LoanQuoteRequest loanRequest, Message msg)
{
this.broker = broker;
this.creditBureauGateway = broker.CreditBureauGateway;
this.bankInterface = broker.BankInterface;
this.processID = processID;
this.loanRequest = loanRequest;
this.message = msg;
CreditBureauRequest creditRequest =
LoanBrokerTranslator.GetCreditBureaurequest(loanRequest);
creditBureauGateway.GetCreditScore(creditRequest,
new OnCreditReplyEvent(OnCreditReply), null);
}
private void OnCreditReply(CreditBureauReply creditReply, Object act)
{
Console.WriteLine("Received Credit Score -- SSN {0} Score {1} Length {2}",
creditReply.SSN, creditReply.CreditScore, creditReply.HistoryLength);
BankQuoteRequest bankRequest =
LoanBrokerTranslator.GetBankQuoteRequest(loanRequest, creditReply);
bankInterface.GetBestQuote(bankRequest, new OnBestQuoteEvent(OnBestQuote), null);
}
private void OnBestQuote(BankQuoteReply bestQuote, Object act)
{
LoanQuoteReply quoteReply = LoanBrokerTranslator.GetLoanQuoteReply
(loanRequest, bestQuote);
Console.WriteLine("Best quote {0} {1}",
quoteReply.InterestRate, quoteReply.QuoteID);
broker.SendReply(quoteReply, message);
broker.OnProcessComplete(processID);
}
}
这些方法不再引用信用局网关和银行网关提供的 ACT 参数,因为所有必要的信息都存储在 LoanBrokerProcess 的实例中。 该过程完成后,它会使用LoanBrokerPM 从 AsyncRequestReplyService继承的SendReply方法发送回复消息。 接下来,它通知LoanBrokerPM该过程已完成。我们可以使用委托来实现此通知,但我们决定使用对代理的引用。
The methods no longer reference the ACT parameter provided by the credit bureau gateway and the bank gateway because all necessary information is stored in the instance of the LoanBrokerProcess. Once the process completes, it sends the reply message using the SendReply method that the LoanBrokerPM inherits from the AsyncRequestReplyService. Next, it notifies the LoanBrokerPM of the completion of the process. We could have implemented this notification using a delegate, but we decided to use a reference to the broker instead.
使用LoanBrokerProcess 类简化了主要的贷款经纪人类:
Using the LoanBrokerProcess class simplifies the main loan broker class:
内部类 LoanBrokerPM :AsyncRequestReplyService
{
受保护的 CreditBureauGateway 信用局网关;
受保护的银行网关银行接口;
受保护的 IDictionary activeProcesses = (IDictionary)(new Hashtable());
公共LoanBrokerPM(字符串请求队列名称,
字符串creditRequestQueueName,字符串creditReplyQueueName,
字符串银行回复队列名称,
BankConnectionManager 连接管理器):base(requestQueueName)
{
CreditBureauGateway = new CreditBureauGateway(creditRequestQueueName,
信用回复队列名称);
CreditBureauGateway.Listen();
BankInterface = new BankGateway(bankReplyQueueName, 连接管理器);
银行接口.Listen();
}
受保护的覆盖类型 GetRequestBodyType()
{
返回类型(LoanQuoteRequest);
}
protected override void ProcessMessage(Object o, Message 消息)
{
贷款报价请求报价请求;
quoteRequest = (LoanQuoteRequest)o;
String processID = 消息.Id;
LoanBrokerProcess 新流程 =
新的LoanBrokerProcess(这个,processID,creditBureauGateway,
银行接口、报价请求、消息);
activeProcesses.Add(processID, newProcess);
}
公共无效OnProcessComplete(字符串进程ID)
{
activeProcesses.Remove(processID);
}
}
internal class LoanBrokerPM : AsyncRequestReplyService
{
protected CreditBureauGateway creditBureauGateway;
protected BankGateway bankInterface;
protected IDictionary activeProcesses = (IDictionary)(new Hashtable());
public LoanBrokerPM(String requestQueueName,
String creditRequestQueueName, String creditReplyQueueName,
String bankReplyQueueName,
BankConnectionManager connectionManager): base(requestQueueName)
{
creditBureauGateway = new CreditBureauGateway(creditRequestQueueName,
creditReplyQueueName);
creditBureauGateway.Listen();
bankInterface = new BankGateway(bankReplyQueueName, connectionManager);
bankInterface.Listen();
}
protected override Type GetRequestBodyType()
{
return typeof(LoanQuoteRequest);
}
protected override void ProcessMessage(Object o, Message message)
{
LoanQuoteRequest quoteRequest;
quoteRequest = (LoanQuoteRequest)o;
String processID = message.Id;
LoanBrokerProcess newProcess =
new LoanBrokerProcess(this, processID, creditBureauGateway,
bankInterface, quoteRequest, message);
activeProcesses.Add(processID, newProcess);
}
public void OnProcessComplete(String processID)
{
activeProcesses.Remove(processID);
}
}
这个LoanBrokerPM基本上是流程。当新消息到达时,它会创建一个新的流程实例。当流程完成时,流程管理器从活动流程列表中删除该流程实例。流程管理器使用消息ID作为分配给每个流程实例的唯一流程ID。现在,我们只需编辑LoanBrokerProcess 类即可更改贷款经纪人的行为,该类除了传递消息对象外,没有对消息传递的引用。看起来关注适当的封装和重构得到了回报。下面的类图总结了贷款经纪人的内部结构:
This LoanBrokerPM is basically a generic implementation of a Process Manager. It creates a new process instance when a new message arrives. When a process completes, the process manager removes the process instance from the list of active processes. The process manager uses the message ID as the unique process ID assigned to each process instance. We can now change the behavior of the loan broker just by editing the LoanBrokerProcess class, which has no references to messaging besides passing the message object around. It looks like paying attention to proper encapsulation and refactoring paid off. The following class diagram summarizes the internal structure of the loan broker:
贷款经纪人类图
Loan Broker Class Diagram
唯一剩下的部分是测试客户端。测试客户端的设计与信用局网关的设计类似。测试客户端可以发出指定数量的重复请求,并将传入响应与未完成的请求相关联。一旦我们启动所有流程(银行、信用局和贷款经纪人),我们就可以执行该示例。我们使用许多简单的主类来启动相应的组件作为控制台应用程序。我们在屏幕上看到一系列活动,表明系统中的消息流(见图)。
The only remaining piece is the test client. The test client design is similar to that of the credit bureau gateway. The test client can make a specified number of repeated requests and correlate incoming responses to outstanding requests. Once we start all processes (banks, credit bureau, and the loan broker), we can execute the example. We use a number of simple main classes to start the respective components as console applications. We see a flurry of activity on the screen, indicating the flow of messages through the system (see figure).
运行 MSMQ 示例
Running the MSMQ Example
现在我们已经运行了完整的解决方案,我们可以收集一些性能指标来比较异步解决方案与同步解决方案的吞吐量。使用测试数据生成器,我们向贷款经纪人发送 50 个随机生成的请求。测试数据生成器报告,接收 50 条回复消息花了 33 秒。
Now that we have the complete solution running, we can gather some performance metrics to compare the throughput of the asynchronous solution to the synchronous solution. Using the test data generator, we send 50 randomly generated requests to the loan broker. The test data generator reports that it took 33 seconds to receive the 50 reply messages.
发送 50 个报价请求
Sending 50 Quote Requests
人们很容易认为每个请求花费了 33/50 = 0.6 秒。错误的!贷款经纪人的吞吐量为33 秒内 50 个请求,但某些请求需要 27 秒才能完成。为什么系统这么慢?我们来看一下测试运行过程中消息队列的快照:
It would be tempting to think that each request took 33/50 = 0.6 seconds. Wrong! The throughput of the loan broker is 50 requests in 33 seconds, but some of the requests took 27 seconds to complete. Why is the system so slow? Let's look at a snapshot of the message queue during the test run:
31 条消息在信用请求队列中排队
31 Messages Are Queued Up in the Credit Request Queue
三十一条消息在信用局请求队列中排队!显然,信用局是我们的瓶颈,因为所有报价请求都必须首先经过信用局。现在我们可以获得松散耦合的一些回报,并启动信用局的两个额外实例。现在,我们正在运行信用局服务的三个并行实例。这应该可以解决我们的瓶颈,对吧?让我们来看看:
Thirty-one messages are queued up in the credit bureau request queue! Apparently, the credit bureau is our bottleneck, because all quote requests have to go through the credit bureau first. Now we can reap some of the rewards of loose coupling and start two additional instances of the credit bureau. We now have three parallel instances of the credit bureau service running. This should fix our bottleneck, right? Let's see:
使用三个信用局实例发送 50 个报价请求
Sending 50 Quote Requests, Using Three Credit Bureau Instances
处理所有 50 条消息的总时间减少到 21 秒,其中最长的请求等待响应时间为 16 秒。平均而言,客户等待贷款请求回复的时间为 8.63 秒,是原始版本的一半。看起来我们消除了瓶颈,但消息吞吐量并没有像我们希望的那样显着增加。但请记住,对于这个简单的示例,我们在单个 CPU 上运行所有进程,以便所有进程竞争相同的资源。让我们看一下新的队列统计数据,以验证信用局的瓶颈实际上已得到纠正:
The total time to process all 50 messages is reduced to 21 seconds, with the longest request waiting for a response for 16 seconds. On average, the client had to wait for a reply to the loan request for 8.63 seconds, half of the original version. It looks like we eliminated the bottleneck, but the message throughput did not increase as dramatically as we might have hoped. Remember, though, that for this simple example we are running all processes on a single CPU so that all processes compete for the same resources. Let's look at the new queue statistics to verify that the credit bureau bottleneck is in fact corrected:
现在 Bank 5 似乎成为瓶颈
Now Bank 5 Appears to Be a Bottleneck
看起来我们消除了一个瓶颈只是为了找到一个新的 Bank 5。为什么是 Bank 5?5号银行是一家当铺;它向所有人提供贷款,因此几乎每个报价请求都包含 Bank 5。我们现在可以继续启动 Bank 5 的多个实例,但期望当铺仅仅为了提高吞吐量而运行多个实例是不现实的。我们的另一个选择是更改银行请求的路由逻辑。由于当铺收取的溢价比其他银行高,因此只有在没有其他银行提供报价的情况下,当铺的报价往往是最低报价。考虑到这一观察结果,如果报价也可以由另一家银行提供服务,我们可以通过不将请求路由到当铺来提高系统效率。
It looks like we eliminated one bottleneck just to find a new oneBank 5. Why Bank 5? Bank 5 is a pawn shop; it offers loans to everybody, so Bank 5 is part of almost every quote request. We could now go on to start multiple instances of Bank 5, but it's not realistic to expect the pawn shop to run multiple instances just to improve our throughput. Our other option is to change the routing logic for the bank requests. Since the pawn shop charges a substantial premium over the other banks, its quote tends to be the lowest quote only in those cases where no other bank provided a quote. Taking this observation into account, we can improve the efficiency of the system by not routing requests to the pawn shop if the quote can also be serviced by another bank. This change will not affect the overall behavior of the system.
我们仅针对无法由任何其他银行提供服务的报价请求更改BankConnectionManager 以包含银行 5。修改后的BankConnectionManager 如下所示:
We change the BankConnectionManager to include Bank 5 only for those quote requests that cannot be serviced by any other bank. The modified BankConnectionManager looks like this:
内部类 BankConnectionManager { 静态受保护的 BankConnection[] 银行 = {new Bank1(), new Bank2(), new Bank3(), new银行4() }; 静态受保护的 BankConnection catchAll = new Bank5(); 公共 IMessageSender[] GetEligibleBankQueues(int CreditScore, int HistoryLength, int 贷款金额) { ArrayList 贷方 = new ArrayList(); for (int 索引 = 0; 索引 < 银行.长度; 索引++) { if (银行[索引].CanHandleLoanRequest(CreditScore, HistoryLength, 贷款额度)) 贷方.Add(银行[index].Queue); } if (lenders.Count == 0) 贷方.Add(catchAll.Queue); IMessageSender[] lenderArray = (IMessageSender [])Array.CreateInstance (typeof(IMessageSender), 贷方.Count); 贷方.CopyTo(lenderArray); 返回贷方数组; } }
internal class BankConnectionManager { static protected BankConnection[] banks = {new Bank1(), new Bank2(), new Bank3(), new Bank4() }; static protected BankConnection catchAll = new Bank5(); public IMessageSender[] GetEligibleBankQueues(int CreditScore, int HistoryLength, int LoanAmount) { ArrayList lenders = new ArrayList(); for (int index = 0; index < banks.Length; index++) { if (banks[index].CanHandleLoanRequest(CreditScore, HistoryLength, LoanAmount)) lenders.Add(banks[index].Queue); } if (lenders.Count == 0) lenders.Add(catchAll.Queue); IMessageSender[] lenderArray = (IMessageSender [])Array.CreateInstance (typeof(IMessageSender), lenders.Count); lenders.CopyTo(lenderArray); return lenderArray; } }
使用修改后的代码运行会产生下图所示的结果。
Running with the modified code produces the results shown in the following figure.
使用三个信用局实例和修改后的 BankConnectionManager 发送 50 个报价请求
Sending 50 Quote Requests Using Three Credit Bureau Instances and a Modified BankConnectionManager
现在的测试结果显示,所有 50 个请求均在 12 秒内得到满足,是原始时间的一半。更重要的是,贷款报价请求的平均处理时间现在不到 4 秒,比初始版本提高了四倍。此示例演示了使用收件人列表进行预测路由的优势。由于贷款经纪人可以控制路由,因此我们可以决定在路由逻辑中构建多少“智能”,而不需要对外部各方进行任何更改。代价是贷款经纪人变得越来越依赖于对内部各方的了解。例如,原来的BankConnectionManager由于对待所有银行一视同仁,修改后的版本依赖于这样一个事实:银行 5 是一个包罗万象的提供商,只有在没有其他选择的情况下才应联系它。如果银行 5 开始提供更好的利率,客户可能不再获得最好的交易。
The test results now show that all 50 requests were serviced in 12 seconds, half of the original time. More importantly, the average time to service a loan quote request is now under 4 seconds, a fourfold improvement over the initial version. This example demonstrates the advantage of predictive routing by using a Recipient List. Because the loan broker has control over the routing, we can decide how much "intelligence" we can build into the routing logic without requiring any changes to the external parties. The trade-off is that the loan broker becomes more and more dependent on knowledge about the internal parties. For example, while the original BankConnectionManager treated all banks as equal, the modified version relies on the fact that Bank 5 is a catch-all provider that should be contacted only if there are no other options. If Bank 5 starts to offer better rates, the clients may no longer get the best possible deal.
该屏幕剪辑还表明,响应消息不一定按照发出请求的顺序到达。我们可以看到测试客户端在收到请求 43 的响应之后立即收到了对请求号 48 的响应。因为我们没有丢失任何响应,这意味着测试客户端在收到响应 43 之前收到了响应 44 到 47。请求传递编号 43?请求 43 似乎已路由至 General Retail Bank(银行 3)。继当铺之后,这家银行的选择标准限制第二少,并且比其他银行更有可能收到请求。如果请求 44 到 47 不符合一般零售银行的标准,则银行网关将收到这些请求的所有响应,银行3队列。因为我们的贷款经纪人是真正的事件驱动型,所以它会在收到所有银行报价后立即回复贷款请求。因此,如果请求 44 的银行报价先于请求 43 的银行报价到达,则贷款经纪人将首先发送请求 44 的回复消息。此场景还强调了消息中相关标识符的重要性,以便测试客户端可以将响应与请求匹配,即使它们到达时顺序不正确。
The screen clip also demonstrates that response messages do not necessarily arrive in the order in which the requests were made. We can see that the test client received the response to request number 48 right after the response to request 43. Because we are not missing any responses, this means that the test client received responses 44 through 47 before it received response 43. How did these requests pass number 43? It looks like request 43 was routed to the General Retail Bank (Bank 3). After the Pawn Shop, this bank has the next least restrictive selection criteria and is more likely than the other banks to be backed up with requests. If requests 44 through 47 did not match the General Retail Bank's criteria, the bank gateway would have received all responses for these requests, while the quote request for request 43 was still sitting in the bank3Queue. Because our loan broker is truly event-driven, it will reply to a loan request as soon as it receives all bank quotes. As a result, if the bank quotes for request 44 arrive before the bank quotes for number 43, the loan broker will send the reply message for request 44 first. This scenario also highlights the importance of the Correlation Identifier in the messages so that the test client can match responses to requests even if they arrive out of order.
调整异步、基于消息的系统可能是一项非常复杂的任务。我们的示例展示了一些识别和解决瓶颈的最基本技术。但即使是我们简单的例子也清楚地表明,纠正一个问题(征信局瓶颈)可能会导致另一个问题(Bank 5 瓶颈)浮出水面。我们还可以清楚地看到异步消息传递和事件驱动消费者的优势。我们能够在 12 秒内处理 50 个报价请求,而同步解决方案则需要 8 或 10 倍的时间!
Tuning asynchronous, message-based systems can be a very complex task. Our example shows some of the most basic techniques of identifying and resolving bottlenecks. But even our simple example made it clear that correcting one problem (the credit bureau bottleneck) can cause another problem (the Bank 5 bottleneck) to surface. We can also clearly see the advantages of asynchronous messaging and event-driven consumers. We were able to process 50 quote requests in 12 secondsa synchronous solution would have taken 8 or 10 times as long!
贷款经纪人示例演示了一个简单的应用程序在变得分布式、异步和事件驱动后如何变得相当复杂。我们现在有十几个类,并始终使用委托来处理异步消息处理的事件驱动性质。复杂性的增加也意味着缺陷风险的增加。异步特性使得这些缺陷难以重现或排除故障,因为它们取决于特定的时间条件。由于这些额外的风险,消息传递解决方案需要非常彻底的测试方法。我们可以写一本关于测试消息传递解决方案的整本书,但现在我想包括一些简单的、可操作的测试建议,总结为以下三个规则:
The loan broker example demonstrates how a simple application can become reasonably complex once it becomes distributed, asynchronous, and event-driven. We now have a dozen classes and use delegates throughout to deal with the event-driven nature of asynchronous message processing. The increased complexity also means increased risk of defects. The asynchronous nature makes these defects hard to reproduce or troubleshoot because they depend on specific temporal conditions. Because of these additional risks, messaging solutions require a very thorough approach to testing. We could write a whole book on testing messaging solutions, but for now I want to include some simple, actionable advice on testing, summarized in the following three rules:
通过使用接口和实现类将应用程序与消息传递实现隔离。
Isolate the application from the messaging implementation by using interfaces and implementation classes.
在将业务逻辑插入消息传递环境之前,使用单元测试用例测试业务逻辑。
Test the business logic with unit test cases before plugging it into the messaging environment.
提供消息传递层的模拟实现,允许您同步测试。
Provide a mock implementation of the messaging layer that allows you to test synchronously.
测试单个应用程序比测试通过消息通道连接的多个分布式应用程序要容易得多。单个应用程序允许我们跟踪完整的执行路径,我们不需要复杂的启动过程来启动所有组件,并且不需要在测试之间清除通道(请参阅Channel Purger [xxx])。有时,在测试其他函数时删除一些外部函数很有用。例如,当我们测试银行网关时,我们可能会删除信用局网关,而不是实际将消息发送到外部信用局进程。
Testing a single application is much easier than testing multiple, distributed applications connected by messaging channels. A single application allows us to trace through the complete execution path, we do not need a complex startup procedure to fire up all components, and there is no need to purge channels between tests (see Channel Purger [xxx]). Sometimes it is useful to stub out some external functions while testing others. For example, while we are testing the bank gateway, we might as well stub out the credit bureau gateway instead of actually sending messages to an external credit bureau process.
我们如何才能实现在单个应用程序内进行测试的一些好处,同时对应用程序代码的影响最小?我们可以将消息网关的实现与接口定义分开。这使我们能够提供该接口的多种实现。
How can we achieve some of the benefits of testing inside a single application with a minimal impact on the application code? We can separate the implementation of a Messaging Gateway from the interface definition. That allows us to provide multiple implementations of the interface.
将征信局接口与实现分离
Separating Credit Bureau Interface from Implementation
因为我们将所有消息传递特定逻辑封装在信用局网关内,所以我们可以定义一个非常简单的接口:
Because we encapsulated all messaging-specific logic inside the credit bureau gateway, we can define a very simple interface:
公共接口 ICreditBureauGateway
{
void GetCreditScore(CreditBureauRequest quoteRequest,
OnCreditReplyEvent OnCreditResponse, 对象 ACT);
无效监听();
}
public interface ICreditBureauGateway
{
void GetCreditScore(CreditBureauRequest quoteRequest,
OnCreditReplyEvent OnCreditResponse, Object ACT);
void Listen();
}
例如,我们可以创建一个模拟信用局网关实现,它实际上并不连接到任何消息队列,而是直接调用GetCreditScore方法内的指定委托。此模拟实现包含与实际信用局相同的逻辑,因此贷款经纪人的其余部分完全不知道此切换。
For example, we can create a mock credit bureau gateway implementation that does not actually connect to any message queue but rather invokes the specified delegate right inside the GetCreditScore method. This mock implementation contains the same logic as the actual credit bureau, so the remainder of the loan broker is completely unaware of this switcheroo.
公共类 MockCreditBureauGatewayImp : ICreditBureauGateway
{
私有随机随机 = new Random();
公共 MockCreditBureauGatewayImp()
{ }
公共无效GetCreditScore(CreditBureauRequest quoteRequest,
OnCreditReplyEvent OnCreditResponse,对象 ACT)
{
CreditBureauReply 回复 = new CreditBureauReply();
回复.CreditScore = (int)(随机.Next(600) + 300);
回复.HistoryLength = (int)(随机.Next(19) + 1);
回复.SSN = quoteRequest.SSN;
OnCreditResponse(回复, ACT);
}
公共无效监听()
{ }
}
public class MockCreditBureauGatewayImp : ICreditBureauGateway
{
private Random random = new Random();
public MockCreditBureauGatewayImp()
{ }
public void GetCreditScore(CreditBureauRequest quoteRequest,
OnCreditReplyEvent OnCreditResponse, Object ACT)
{
CreditBureauReply reply = new CreditBureauReply();
reply.CreditScore = (int)(random.Next(600) + 300);
reply.HistoryLength = (int)(random.Next(19) + 1);
reply.SSN = quoteRequest.SSN;
OnCreditResponse(reply, ACT);
}
public void Listen()
{ }
}
CreditBureau 类的实现展示了消息传递相关函数(封装在基类中)和业务逻辑(在我们的简单示例中简化为随机生成器)的清晰分离。在现实场景中,业务逻辑(希望)会更加复杂。在这种情况下,将getCreditScore和getCreditHistoryLength方法一起移动到一个单独的类中是值得的,该类对消息传递层没有任何依赖关系(尽管不太明显,继承仍然携带从子类到基类和相关的依赖关系)类)。然后我们可以使用单元测试工具,例如nUnit(http://www.nunit.org)编写测试用例而不必担心消息传递。
The implementation of the CreditBureau class demonstrated a clean separation of messaging-related functions (encapsulated in the base class) and the business logic (reduced to a randomizer in our simple example). In a real-life scenario, the business logic would (hopefully) be somewhat more complex. In that case, it pays off to move the getCreditScore and getCreditHistoryLength methods together into a separate class that does not have any dependency on the messaging layer (even though it is less visible, inheritance still carries dependencies from the subclass to the base class and related classes). We can then use a unit test tool such as nUnit (http://www.nunit.org) to write test cases without having to worry about messaging.
ICreditBureauGateway的模拟实现简单而有效。但它也替换了所有与信用局网关相关的代码,因此必须单独测试类。如果我们想要消除对消息队列的依赖(以及相关的性能影响),但仍执行 CreditBureauGatewayImp 类内的代码,则可以使用IMessageReceiver和 IMessageSender接口的模拟实现。一个简单的模拟实现可能如下所示:
The mock implementation of the ICreditBureauGateway is simple and effective. But it also replaces all credit bureau gatewayrelated code so that the class CreditBureauGatewayImp has to be tested separately. If we want to eliminate the dependency (and the associated performance hit) on message queues but still execute the code inside the CreditBureauGatewayImp class, we can use a mock implementation of the IMessageReceiver and IMessageSender interfaces. A simple mock implementation could look like this:
公共类 MockQueue:IMessageSender、IMessageReceiver
{
私有 OnMsgEvent onMsg = new OnMsgEvent(DoNothing);
公共无效发送(消息msg){
onMsg(消息);
}
私有静态无效DoNothing(消息msg){
}
公共 OnMsgEvent OnMessage
{
获取{返回onMsg; }
设置 { onMsg = 值; }
}
公共无效开始()
{
}
公共消息队列 GetQueue()
{
返回空值;
}
}
public class MockQueue: IMessageSender, IMessageReceiver
{
private OnMsgEvent onMsg = new OnMsgEvent(DoNothing);
public void Send(Message msg){
onMsg(msg);
}
private static void DoNothing(Message msg){
}
public OnMsgEvent OnMessage
{
get { return onMsg; }
set { onMsg = value; }
}
public void Begin()
{
}
public MessageQueue GetQueue()
{
return null;
}
}
我们可以看到,Send立即触发了onMsg委托,而没有使用任何消息队列。要将此模拟队列用于信用局网关,我们必须确保回复正确类型的消息。我们无法简单地将请求消息传递回回复消息。此实现未在此处显示,但如果例如我们使用预设回复消息,则可以很简单。
We can see that the Send triggers the onMsg delegate immediately without using any message queue. To use this mock queue for the credit bureau gateway, we would have to make sure to reply with a message of the correct type. We would not be able to simply pass the request message back to the reply message. This implementation is not shown here but can be simple if, for example, we use a canned reply message.
本节提醒我们,由于组件之间的异步特性和松散耦合,即使是简单的消息传递系统(贷款经纪人实际上只需执行两个步骤:获取信用评分和获得最佳银行报价)也会变得相当复杂。然而,我们仍然采取了一些快捷方式来保持示例的易于管理。具体来说,该示例不涉及以下主题:
This section reminded us that even a simple messaging system (the loan broker really has to execute only two steps: get the credit score and get the best bank quote) can get fairly complex due to the asynchronous nature and loose coupling between components. However, we still took a number of shortcuts to keep the example manageable. Specifically, the example does not address the following topics:
错误处理
Error handling
交易
Transactions
线程安全
Thread safety
该示例没有处理错误的托管机制。此时,组件只是将消息吐出到各个控制台窗口中,这对于生产系统来说不是合适的解决方案。对于真正的实施,错误消息应路由到中央控制台,以便它们能够以统一的方式通知操作员。第 11 章“系统管理”(例如,控制总线[xxx])中的系统管理模式满足了这些要求。
The example has no managed mechanism to handle errors. At this point, components simply spit out messages into the various console windowsnot a suitable solution for a production system. For a real implementation, error messages should be routed to a central console so they can notify an operator in a unified way. The systems management patterns in Chapter 11, "System Management" (e.g., the Control Bus [xxx]) address these requirements.
此示例不使用事务队列。例如,如果MessageRouter 在向银行发送四分之二的报价请求消息后崩溃,则某些银行将处理报价请求,而其他银行则不会。同样,如果贷款经纪人在收到所有银行报价回复后但在向客户发送回复之前崩溃,则客户将永远不会收到回复。在现实系统中,此类操作需要封装在事务内,以便在发送相应的出站消息之前不会使用传入消息。
This example does not use transactional queues. For example, if the MessageRouter crashes after sending two out of four quote request messages to the banks, some banks will process a quote request, while others will not. Likewise, if the loan broker crashes after it receives all bank quote replies but before it sends a reply to the client, the client will never receive a reply. In a real-life system, actions like these need to be encapsulated inside transactions so that incoming messages are not consumed until the corresponding outbound message had been sent.
贷款经纪人实现仅在单个线程中执行,并且不担心线程安全。例如,在前一条消息的处理完成之前,不会调用入站消息队列(隐藏在MessageReceiverGatewayBeginReceive 方法。这对于示例应用程序来说很好(并且比同步实现快得多),但对于高吞吐量环境,我们希望使用管理多个执行者线程的消息调度程序。
The loan broker implementation executes only in a single thread and does not worry about thread safety. For example, the BeginReceive method on an inbound message queue (hidden away in MessageReceiverGateway) is not called until the processing of the previous message has been completed. This is just fine for an example application (and a lot faster than the synchronous implementation), but for a high-throughput environment, we would want to use a Message Dispatcher that manages multiple performer threads.
本章引导我们完成了使用异步消息队列和 MSMQ 的贷款经纪人应用程序的实现。我们故意不回避展示实现细节,以便揭示构建异步消息应用程序所固有的真正问题。我们更关注设计权衡,而不是特定于供应商的消息 API,因此该示例对于非 C# 开发人员也很有价值。
This chapter walked us through the implementation of the loan broker application using asynchronous message queues and MSMQ. We intentionally did not shy away from showing implementation details in order to bring the real issues inherent in building asynchronous messaging applications to light. We focused on the design trade-offs more so than on the vendor-specific messaging API so that the example is also valuable for non-C# developers.
这个例子提醒我们实现一个简单的消息应用程序的复杂性。在使用异步消息传递时,在整体应用程序中理所当然的许多事情(例如,调用方法)可能需要大量的编码工作。幸运的是,设计模式为我们提供了一种语言来描述一些设计权衡,而不必深入了解供应商术语。
This example reminds us of the complexities of implementing even a simple messaging application. Many things that can be taken for granted in a monolithic application (e.g., invoking a method) can require a significant amount of coding effort when using asynchronous messaging. Luckily, the design patterns provide us with a language to describe some of the design trade-offs without having to descend too deeply into the vendor jargon.
作者:迈克尔·J·雷蒂格
by Michael J. Rettig
贷款经纪人的前两个实现使用提供基本消息通道功能的集成框架。例如,Axis 和 MSMQ 都提供了 API 来向消息通道发送消息或从消息通道接收消息,而应用程序必须处理几乎所有其他事情。我们特意选择这种类型的实现来演示如何使用常用的 Java 或 C# 库从头开始构建集成解决方案。
The previous two implementations of the loan broker used integration frameworks that provided basic Message Channel functionality. For example, both Axis and MSMQ provided APIs to send messages to or receive messages from a Message Channel , while the application had to take care of pretty much everything else. We chose this type of implementation intentionally to demonstrate how an integration solution can be built from the ground up, using commonly available Java or C# libraries.
许多商业 EAI 产品套件提供了更多的功能来简化集成解决方案的开发。这些产品套件通常包括可视化开发环境,允许消息转换器和流程管理器的拖放配置。许多还提供复杂的系统管理和元数据管理功能。我们选择 TIBCO ActiveEnterprise 集成套件用于此示例实施。与之前的实现一样,我们主要关注设计决策和权衡,并且仅引入理解解决方案所需的特定于产品的语言。因此,即使您以前没有使用过 TIBCO ActiveEnterprise,本节也应该很有用。如果您对详细的产品或供应商信息感兴趣,请访问http://www.tibco.com。
Many commercial EAI product suites offer substantially more functionality to streamline the development of integration solutions. These product suites typically include visual development environments that allow for drag-drop configuration of Message Translators and Process Managers. Many also provide sophisticated systems management and metadata management functions. We chose the TIBCO ActiveEnterprise integration suite for this example implementation. As with the previous implementations, we focus primarily on design decisions and trade-offs and introduce only as much product-specific language as is necessary to understand the solution. Therefore, this section should be useful even if you have not worked with TIBCO ActiveEnterprise before. If you are interested in detailed product or vendor information, please visit http://www.tibco.com.
此示例实现在解决方案设计上也有所不同,因为它使用了拍卖式的分散-收集方法。此方法使用发布-订阅通道而不是接收者列表,以便贷款经纪人可以向任意数量的银行发送报价请求。这种类型的分散-聚集模式对未知数量的侦听器执行动态请求-应答。此外,贷款经纪人组件的实现使用了 TIBCO流程管理器工具提供的业务流程管理功能。
This example implementation also differs in the solution design by using an Auction-style Scatter-Gather approach. This approach uses a Publish-Subscribe Channel instead of a Recipient List so that the loan broker can send the quote request to any number of banks. This type of Scatter-Gather pattern performs a dynamic Request-Reply with an unknown number of listeners. Additionally, the implementation of the loan broker component uses the business process management functionality provided by TIBCO's Process Manager tool.
我们的应用程序是一个简单的银行报价请求系统。客户向贷款经纪人界面提交报价请求。贷款经纪人通过首先获得信用评分来满足请求,然后向多家银行请求报价。一旦贷款经纪人从银行获得报价,它就会选择最佳报价并将其返回给客户(见图)。
Our application is a simple bank quote request system. Customers submit quote requests to a loan broker interface. The loan broker fulfills the request by first obtaining a credit score, then requesting quotes from a number of banks. Once the loan broker obtains quotes from the banks, it selects the best quote and returns it to the customer (see figure).
系统的客户端期望贷款经纪人有一个同步的请求-答复接口——客户端发送报价请求并等待来自贷款经纪人的答复消息。贷款经纪人又使用请求-答复接口与信用局通信以获得信用评分。收到初始客户端请求后,贷款经纪人可以选择在分布式同步外观后面执行异步操作。这使得贷款经纪人能够利用带有发布-订阅通道的拍卖式分散-收集来获取来自多家银行的银行报价。
Clients of the system expect a synchronous Request-Reply interface to the loan brokerthe client sends a quote request and waits for a reply message from the loan broker. The loan broker in turn uses a Request-Reply interface to communicate with the credit bureau to obtain credit scores. Once the initial client request is received, the loan broker has the option to perform asynchronous operations behind the distributed, synchronous facade. This allows the loan broker to utilize an auction-style Scatter-Gather with a Publish-Subscribe Channel to acquire the bank quotes from multiple banks.
TIBCO 贷款经纪人解决方案架构
TIBCO Loan Broker Solution Architecture
拍卖序列首先将报价请求消息发布到bank.loan.request 发布-订阅通道,以便任何感兴趣的银行都可以侦听该消息并提供自己的利率。提交回复的银行数量未知,并且每个报价请求的银行数量可能有所不同。拍卖的工作原理是向银行开放拍卖,然后等待预定的时间来获取通道上的响应消息bank.loan.reply。每次收到出价后,超时都会重置,以便其他银行有时间在可能的情况下提交另一个出价。在这种情况下,其他银行的出价是公开的,因此另一家银行实际上可以听取其他出价,并在需要时提出还价。
The auction sequence begins by publishing the request for quotes message to the bank.loan.request Publish-Subscribe Channel so that any interested bank can listen for the message and provide its own rates. The number of banks submitting replies is unknown and can vary for each quote request. The auction works by opening the auction to the banks, then waiting a predefined amount of time for response messages on the channel bank.loan.reply. Each time a bid is received, the timeout is reset, giving other banks time to submit another bid if possible. In this case, the bids of other banks are public, so another bank could actually listen for other bids and place a counter bid if desired.
贷款经纪人向未知数量的接收者广播报价请求。这与收件人列表完全不同,后者涉及将请求发送到预定义的银行列表。这反映在聚合器的完整性条件中。聚合器不再像以前的实现那样等待每个银行的响应,而是仅依靠超时条件来终止拍卖。聚合器只是忽略拍卖超时后收到的任何响应。如果在指定的时间间隔内没有银行回复,聚合器将向客户端发送一条响应消息,表明未获得报价。
The loan broker broadcasts the request for a quote to an unknown number of recipients. This is quite different from a Recipient List , which involves sending the request to a predefined list of banks. This is reflected in the Aggregator's completeness condition. Instead of waiting for a response from each bank as in the previous implementations, the Aggregator relies solely on a timeout condition to terminate the auction. The Aggregator simply ignores any responses received after the auction times out. If no bank replies within the specified interval, the Aggregator sends a response message to the client indicating that no quotes were obtained.
描述流程管理器行为的活动图
Activity Diagram Describing the Process Manager Behavior
在此实现中,内容丰富器和聚合器功能在流程管理器组件内实现。因此,解决方案架构图没有描述组件之间交互的细节,因为它们嵌入在单个组件中。相反,我们需要查看表示流程管理器使用的流程模板定义的活动图。初始活动图清楚地定义了贷款经纪人的角色,并为流程模板提供了基础。从图中我们可以看出贷款经纪人有几个职责。这可以很好地转化为流程图,以图形方式显示事件和决策路径的确切顺序。
In this implementation, the Content Enricher and Aggregator functions are implemented within the Process Manager component. As a result, the solution architecture diagram does not describe the details of the interaction between the components, because they are embedded in a single component. Instead, we need to look at the activity diagram that represents the Process Template definition used by the Process Manager. An initial activity diagram cleanly defines the role of the loan broker and provides the basis for the Process Template. From the diagram, we can see that the loan broker has several responsibilities. This translates well into a process diagram, which graphically shows the exact order of events and decision paths.
为了解释我们解决方案的设计,我们需要介绍一些有关 TIBCO 产品套件的基本概念。实施 TIBCO 解决方案通常需要针对特定问题评估不同的工具,因为有时可以使用多种不同的工具来解决一个问题。诀窍是选择最好的一个。我们将讨论仅限于构建示例实现所需的 TIBCO 功能:
In order to explain the design of our solution, we need to introduce some basic concepts about the TIBCO product suite. Implementing TIBCO solutions often requires evaluating different tools for a particular problem because at times, a problem can be solved with several different tools. The trick is picking the best one. We limit the discussion to only those TIBCO features that are required to construct the example implementation:
TIB/RendezVous 运输
TIB/RendezVous Transport
TIB/IntegrationManager 流程管理器工具
TIB/IntegrationManager Process Manager tool
用于元数据管理的 TIBCO 存储库
TIBCO Repository for Metadata Management
TIBCO 消息传递套件的核心是 TIB/RendezVous 传输层。RendezVous 提供了在信息总线上发送和接收 TIBCO 消息的消息传递机制。TIBCO 支持广泛的传输,包括(但不限于)JMS、HTTP、FTP 和电子邮件。RendezVous 为我们示例中的消息提供底层传输。该传输支持同步和异步消息以及点对点通道和发布-订阅通道。每个通道可以配置为不同的服务级别:
At the heart of TIBCO's messaging suite is the TIB/RendezVous transport layer. RendezVous provides the messaging mechanism for sending and receiving TIBCO messages on the information bus. TIBCO supports a wide range of transports, including (but not limited to) JMS, HTTP, FTP, and e-mail. RendezVous provides the underlying transport for the messages in our example. The transport supports both synchronous and asynchronous messages as well as Point-to-Point Channels and Publish-Subscribe Channels. Each channel can be configured for different service levels:
可靠消息传递 (RV) 提供高性能,但存在丢失消息的现实风险。
Reliable Messaging (RV) provides high performance, but at the realistic risk of losing messages.
认证消息(RVCM) 至少传送一次。
Certified messages (RVCM) at least once delivery.
事务性消息传送 (RVTX) 保证一次且仅一次传送。
Transactional messaging (RVTX) guaranteed once and only once delivery.
与本书中的 MSMQ 示例类似,TIBCO 提供了一个开放 API,用于使用 Java 或 C++ 创建消息传递解决方案,并包含一系列用于简化开发过程的工具。
Similar to the MSMQ examples in this book, TIBCO provides an open API for creating messaging solutions using Java or C++ and includes a range of tools for simplifying the development process.
TIB/IntegrationManager 是一种 TIBCO 开发工具,由用于设计工作流程解决方案的丰富用户界面和用于执行这些解决方案的流程管理器引擎组成。GUI 提供了广泛的配置、工作流程和实施选项,这些选项存储在 TIBCO 存储库中。TIBCO 使用存储库作为系统的中央配置工件。它保存所有元数据、工作流程和自定义代码。
TIB/IntegrationManager is a TIBCO development tool consisting of a rich user interface for designing workflow solutions and a Process Manager engine for executing them. The GUI provides an extensive array of configuration, workflow, and implementation options that are stored in the TIBCO Repository. TIBCO uses the repository as the central configuration artifact for the system. It holds all metadata, workflow, and custom code.
该解决方案的工作流组件是其区别于代码级解决方案的一个方面。对于贷款经纪人示例,TIB/IntegrationManager 提供异步和同步消息传递以及基本工作流程,包括服务器会话状态[ EAA ]。出于我们的目的,我们可以将 TIB/IntegrationManager 分为三个部分:通道、作业创建器和流程图。
The workflow component of the solution is an aspect that sets it apart from code-level solution. For the loan broker example, TIB/IntegrationManager provides asynchronous and synchronous messaging as well as basic workflow, including Server Session State [EAA]. For our purposes, we can break TIB/ IntegrationManager into three parts: the channel, the job creator, and the process diagram.
TIB /IntegrationManager 组件
TIB/IntegrationManager Components
TIB/IntegrationManager 中的每个进程都会创建一个“作业”(或进程实例),该“作业”提供用于维护状态的中央会话对象。该对象包括一个带有用于存储对象的 get 和 put 操作的槽环境,以及用于与会话交互的实用方法。简单的 GUI 允许 TIBCO 开发人员创建流程定义(在 TIBCO 中称为流程图)指定作业执行的任务顺序。流程定义类似于 UML 样式的活动图,由通过转换线连接的任务组成(参见下一页的图)。然而,这些图表仅提供了框架;每个活动(即图中的框)背后都有大量的配置和代码。对于我们的示例,我们需要一些代码来处理贷款。TIB/IntegrationManager 使用 ECMAScript(通常称为 JavaScript)作为底层脚本语言。
Every process in TIB/IntegrationManager creates a "job" (or process instance) that provides a central session object for maintaining state. This object includes a slotted environment with get and put operations for storing objects, as well as utility methods for interacting with the session. A simple GUI allows TIBCO developers to create process definitions (called Process Diagrams in TIBCO) that specify the sequence of tasks the job executes. Process definitions resemble UML-style activity diagrams consisting of tasks connected by transition lines (see figure on the next page). However, the diagrams provide only the skeleton; behind each activity (i.e., box on the diagram) is a good amount of configuration and code. For our example, we require some code for processing the loans. TIB/IntegrationManager uses ECMAScript (commonly referred to as JavaScript) as the underlying scripting language.
TIB /IntegrationManager 流程图示例
TIB/IntegrationManager Process Diagram Example
典型的流程图包括一系列集成任务。其中包括控制任务(例如分叉、同步条或决策点)、发送和接收消息的信号任务以及执行任务(例如数据转换、路由和系统集成)。此处所示的基本图包括两个任务:执行一些自定义逻辑的 ECMAScript 任务和将消息发布到通道的信号输出任务。任务转换可以包含用于根据消息内容或其他标准选择特定路由的逻辑。
A typical process diagram includes a series of integration tasks. These include control tasks such as forks, sync bars or decision points, signal tasks to send and receive messages, and execution tasks such as data translation, routing, and system integration. The basic diagram pictured here includes two tasks: an ECMAScript task that executes some custom logic and a signal-out task that publishes a message to a channel. Task transitions can contain logic for selecting a particular route based upon message contents or other criteria.
集成和消息传递几乎总是需要某种形式的自描述数据(请参阅第 8 章“消息转换”中的“简介”)。TIBCO 将元数据类定义为存储在 TIBCO 存储库中的 ActiveEnterprise (AE) 对象。通过消息通道发送的每个 AE 对象都包含一个格式指示符,以指示该消息遵循的类定义。
Integration and messaging nearly always requires some form of self-describing data (see "Introduction" in Chapter 8, "Message Transformation"). TIBCO defines metadata classes as ActiveEnterprise (AE) objects stored in the TIBCO repository. Every AE object sent across a message channel includes a Format Indicator to indicate the class definition that this message adheres to.
管理消息元数据是开发过程的重要组成部分。TIBCO 开发人员可以直接在开发环境中定义元数据,可以从关系数据库等外部系统中提取元数据(使用通道适配器 [ xxx] 中描述的元数据适配器),或者导入 XML 模式。元数据为系统中的对象和消息提供了明确的契约。一旦在 TIBCO 存储库中定义了类,这些对象就可以在 ECMA 脚本中进行实例化和操作:
Managing message metadata is an important part of the development process. TIBCO developers can define the metadata directly within the development environment, can extract it from external systems such as relational databases (using a Metadata Adapter as described in Channel Adapter [xxx]), or import XML schemas. The metadata provides an explicit contract for objects and messages in the system. Once the classes are defined in the TIBCO repository, the objects are available for instantiation and manipulation within ECMA script:
//TIBCO AE类的实例化 var Bank = new aeclass.BankQuoteRequest(); 银行.CorrelationID = job.generateGUID(); 银行.SSN = 工作.请求.SSN;
//Instantiation of a TIBCO AE class var bank = new aeclass.BankQuoteRequest(); bank.CorrelationID = job.generateGUID(); bank.SSN = job.request.SSN;
但是,请记住,这是一个动态的脚本环境,编译时类型检查有限。更改元数据定义很容易破坏系统的其他部分。如果没有适当的测试和开发实践,基于消息的系统很容易成为“仅添加”系统,这意味着元数据仅添加到系统中并且永远不会更改,因为担心会破坏某些内容。
However, remember that this is a dynamic, scripted environment with limited compile-time type checking. Changing a metadata definition can easily break another part of your system. Without proper testing and development practices, message-based systems can easily become "add-only" systems, meaning metadata is only added to the system and never changed for fear of breaking something.
第 446 页的解决方案架构图向我们展示了该解决方案需要以下服务。
The Solution Architecture diagram, on page 446, shows us that the solution requires the following services.
贷款经纪人
Loan Broker
收到初始请求
Receives the initial request
获得信用评分
Obtains a credit score
与银行举行贷款拍卖
Holds loan auction with banks
返回最佳贷款报价给客户
Returns best loan offer to client
信用服务
Credit Service
提供基于 SSN 的信用评分
Provides a credit score based upon SSN
银行
Bank(s)
根据信用评级和贷款金额提交报价
Submit a quote based upon the credit rating and loan amount
每个服务都可以通过外部接口访问。对于每个这样的接口,我们必须做出以下设计决策:
Each service is accessible through an external interface. For each such interface, we have to make the following design decisions:
对话风格:同步与异步
Conversation Style: Synchronous vs. Asynchronous
服务质量水平
Quality of Service Level
界面的对话风格已由解决方案架构预先确定。贷款经纪人和信贷服务都需要同步接口,而与银行的通信将是纯粹异步的。
The conversation styles for the interfaces have been predetermined by the solution architecture. The loan broker and credit service will both need synchronous interfaces, while the communication with the banks will be purely asynchronous.
消息传递解决方案的服务级别可能变得相当复杂,尤其是在故障转移场景下。幸运的是,贷款示例服务级别解析为一个简单的解决方案。如果发生故障(超时、消息丢失、系统停机),可以重新提交原始请求(贷款经纪人是幂等接收者[xxx])。请记住,我们此时仅获取报价。它还不是具有法律约束力的协议。当然,这种类型的假设需要记录在系统中并被所有相关方理解。例如,银行需要知道报价请求可能会再次重新提交。如果银行出于欺诈检测目的跟踪客户贷款请求,我们可能必须更改解决方案以避免发送重复请求,例如使用保证交付。
Service levels for messaging solutions can become quite complicated, especially given failover scenarios. Fortunately, the loan example service level resolves to a simple solution. In event of failure (timeout, dropped messages, down systems), the original request can be resubmitted (the loan broker is an Idempotent Receiver [xxx]). Remember that we are only obtaining a quote at this point. It is not yet a legally binding agreement. Of course, this type of assumption needs to be documented in the system and understood by all parties involved. For example, the banks need to know that a quote request may be resubmitted a second time. If the banks track customer loan requests for fraud detection purposes, we may have to change the solution to avoid sending duplicate requests, for example by using Guaranteed Delivery.
贷款经纪人系统在客户和贷款经纪人之间以及贷款经纪人和征信局之间有两个同步接口。TIBCO 通过使用Request-Reply和Command Message的操作来实现 RPC 风格的消息传递。本质上,这是底层 TIBCO 消息传递引擎的同步包装器。在调用 TIBCO 操作期间,您可以侦听通过总线传递的请求和回复消息。请求消息发布在指定通道上(例如customer.loan.request )。该消息包含返回地址它指定用于回复的所谓 INBOX 通道的地址。TIBCO 将异步细节隐藏在 RPC 风格的编程模型后面。在 TIB/IntegrationManager 中,诸如获取信用评分之类的域操作被定义为 AE 类的一部分,并且可以从流程建模工具中调用。例如,要将贷款经纪人公开为同步接口,我们必须执行几个实现步骤。信用局服务的实施严格遵循这些相同的步骤。
The loan broker system has two synchronous interfacesbetween the customer and the loan broker and between the loan broker and the credit bureau. TIBCO implements RPC-style messaging with operations using Request-Reply and Command Message. Essentially, this is a synchronous wrapper to the underlying TIBCO messaging engine. During the invocation of TIBCO operations, you can listen to the request and reply messages passing across the bus. The request message is published on the specified channel (for example, customer.loan.request). The message includes a Return Address that specifies the address of a so-called INBOX channel for the reply. TIBCO hides the asynchronous details behind an RPC-style programming model. In TIB/IntegrationManager, the domain operations such as obtain credit score are defined as part of the AE classes and can be invoked from the process modeling tool. For example, to expose the loan broker as a synchronous interface, we must perform several implementation steps. The implementation of the credit bureau service follows closely to these same steps.
AE 类定义通过 TIB/RendezVous 通道发送的消息的数据格式。在 TIB/IntegrationManager 中定义类与在您最喜欢的 IDE 中创建类有很大不同。AE 类是通过 TIB/IntegrationManager IDE 中的一系列对话框(见图)定义的。对话框允许您为类选择名称,然后为类指定字段。这些字段可以输入为整数、浮点数或双精度数,也可以由其他 AE 类组成。
AE classes define the data format for messages sent across TIB/RendezVous channels. Defining a class in TIB/IntegrationManager is quite different from creating a class in your favorite IDE. AE classes are defined through a series of dialog boxes (see figure) from the TIB/IntegrationManager IDE. The dialog boxes allow you to select a name for your class and then designate fields for the class. The fields can be typed as integers, floats, or doubles, or can be composed of other AE classes.
使用属性和操作定义AE类
Defining an AE Class with Attributes and Operations
与添加接口方法类似,可以将操作添加到 AE 类中。可以在定义中指定参数和返回类型。对于接口来说,没有指定任何实现。当我们使用作业创建器将通道绑定到流程实例时,会稍后绑定实现。
Similar to adding interface methods, operations can be added to an AE class. The parameters and the return types can be specified in the definition. True to an interface, no implementation is specified. The implementation is bound later when we use the job creator to bind a channel to a process instance.
流程图提供了操作的实现。本例中实现的操作需要返回参数。与代码中实现的方法不同,流程图没有“返回”值。相反,我们指定作业中将放置返回值的槽。我们在作业创建者中指定了这一点,并且我们必须记住在流程图中将值正确分配给作业槽。流程图的实际实现将在以下几页中详细讨论。
A process diagram provides the implementation for the operation. The operations implemented in this example require a return parameter. Unlike methods implemented in code, a process diagram doesn't have a "return" value. Instead, we specify the slot in the job where the return value will be placed. We designate this in the job creator, and we must remember to properly assign the value to the job slot in our process diagram. The actual implementation of the process diagram is discussed in detail in the following pages.
该通道允许我们定义传输、消息定义、服务和主题。对于我们的同步操作,我们需要一个客户端/服务器通道。我们可以指定在步骤 1 中创建的 AE 类。从最初的接口定义中,我们选择了具有可靠消息传递的 RendezVous。配置这些选项只需单击并选择适当的选项即可。
The channel allows us to define our transport, message definition, service, and subject. For our synchronous operations, we need a client/server channel. We can specify the AE classes that we created in step 1. From our initial interface definitions, we chose RendezVous with reliable messaging. Configuring these options is just a matter of clicking and selecting the proper options.
定义通道属性
Defining Channel Properties
作业创建者从通道检索值,并通过创建作业并初始化其环境槽将它们传递到流程图。创建作业后,流程图就会被实例化。执行将遵循活动图定义的路径。流程完成后,作业创建者会将回复返回给通道。我们可以在作业创建者对话框中看到我们操作的名称。
The job creator retrieves values from the channel and passes them to the process diagram by creating a job and initializing its environment slots. Once the job is created, the process diagram is instantiated. Execution will follow the path defined by the activity diagram. Once the process finishes, the job creator returns the reply back to the channel. We can see in the job creator dialog the name of our operation.
配置作业创建者
Configuring the Job Creator
有了我们的同步服务,我们就可以实施贷款经纪人将一切联系在一起。所包含的图表代表了贷款经纪人的流程。该过程定义了贷款经纪人组件的行为。在之前的步骤中,我们定义了 AE 操作、通道和作业创建者,以便在客户端向 CreditRequest 通道提交消息时实例化流程图。
With our synchronous services in place, we can implement the loan broker to tie everything together. The included diagram represents the loan broker process. This process defines the behavior of the loan broker component. In our prior steps, we defined the AE operation, channel, and job creator to instantiate the process diagram when a client submits a message to the CreditRequest channel.
从设计的角度来看,我们需要非常小心流程图中包含的内容。大型、复杂的图表很快就会变得难以管理。在图表中创建有效的关注点分离至关重要,避免单个流程定义中业务逻辑和流程逻辑的丑陋混合。定义什么是流程逻辑和什么是业务逻辑很困难。一个好的经验法则是将流程逻辑视为外部系统交互:接下来我要连接到哪个系统?如果系统不可用怎么办?业务逻辑通常会涉及更多特定领域的语言:如何激活订单?如何计算信用评分?如何创建银行贷款报价?考虑到业务代码的复杂性,功能齐全的开发语言往往是实现的最佳选择。大多数流程管理工具(包括 TIB/IntegrationManager)允许您直接与 Java 或其他语言集成。
From a design perspective, we need to be very careful as to what is included in the process diagram. Large, complex diagrams quickly become unmanageable. It is critical to create an effective separation of concerns within the diagrams, avoiding an ugly mixture of business logic and process logic inside a single process definition. Defining what is process logic and what is business logic is hard. A good rule of thumb is to think of process logic as external system interaction: What system do I connect to next? What do I do if the system is not available? Business logic will typically involve more domain-specific language: How do I activate an order? How do I calculate a credit score? How do I create a quote for a bank loan? Given the complex nature of business code, a full-featured development language is often the best choice for implementation. Most process management tools, including TIB/IntegrationManager, allow you to integrate directly with Java or another language.
对于那些熟悉 MVC(模型、视图、控制器)概念(例如,[ POSA ])的人来说,可以在类似的上下文中查看实现。通过简单地将视图重命名为工作流,我们的实现就陷入了简洁的定义。
For those familiar with the MVC (Model, View, Controller) concept (for example, [POSA]), the implementation can be viewed in a similar context. By simply renaming view to workflow, our implementation falls into a concise definition.
工作流是 工作流的可视化模型。
Workflow a visualized model of the workflow.
控制器是 从消息总线接收事件的流程引擎,执行流程工作流的适当组件。
Controller the process engine that receives events from the message bus, executing the proper component of the process workflow.
对底层业务代码(ECMAScript、JavaScript、Java、J2EE 等)进行建模。
Model the underlying business code (ECMAScript, JavaScript, Java, J2EE, etc.).
下图包含脚本执行框和执行集成操作的自定义任务的组合。使用可视化流程建模工具对流程进行建模的一大优势是运行的“代码”看起来与我们用来设计解决方案的 UML 活动图非常相似。
The following diagram contains a mix of script execution boxes and custom tasks that perform integration actions. One of the big advantages of modeling processes using a visual process modeling tool is that the running "code" looks very similar to the UML activity diagram that we used to design the solution.
贷款经纪人流程定义
The Loan Broker Process Definition
由于每个任务图标都可以包含实际代码或重要的配置参数,因此我们将逐步介绍每个任务并更详细地描述它。
Since each task icon can contain actual code or important configuration parameters, we'll walk through each task and describe it in more detail.
第一个框代表一个 ECMAScript,它实例化AE对象并向字段分配值。
The first box represents an ECMAScript that instantiates an AE object and assigns values to the fields.
var Credit = new aeclass.CreditBureauRequest(); 信用.SSN = 工作.请求.SSN; job.creditRequest = 信用;
var credit = new aeclass.CreditBureauRequest(); credit.SSN = job.request.SSN; job.creditRequest = credit;
创建的信用请求需要作为下一个活动的参数,该活动将调用对信用局的同步操作调用。信用局作为一个单独的流程图实现,由接收消息并返回信用评分的单个 ECMA 任务组成。同步操作意味着贷款经纪人进程将等待信用局的回复消息到达。
The credit request created is needed as a parameter in the next activity, which invokes the synchronous operations call to the credit bureau. The credit bureau is implemented as a separate process diagram consisting of a single ECMA task that receives the message and returns a credit score. The synchronous operation implies that the Loan Broker process will wait until a reply message from the credit bureau arrives.
一旦贷款经纪人收到信用局的回复,我们可以在图中看到需要创建另一个 AE 对象来向任何参与银行发布报价请求。为了实现此功能,我们使用Mapper任务,它允许我们以图形方式映射来自多个源的数据项。映射器是消息转换器模式的可视化实现。Mapper任务展示了管理元数据的优点之一。由于 TIB/IntegrationManager 可以访问定义每个对象结构的元数据,因此Mapper显示源对象和目标对象的结构,并允许我们通过单击几下鼠标直观地映射字段。使用输入对象的值实例化一个新对象。我们使用作业对象的generateGUID 方法生成唯一的相关标识符并将其分配给bankRequest。正如我们稍后将看到的,关联 ID 字段非常重要,它允许我们同时处理多个贷款请求。
Once the loan broker receives a reply from the credit bureau, we can see in the diagram that another AE object needs to be created to publish a quote request to any participating banks. To implement this function, we use the Mapper task that allows us to graphically map data items from multiple sources. The Mapper is a visual implementation of the Message Translator pattern. The Mapper task demonstrates one of the advantages of managing metadata. Because TIB/IntegrationManager has access to the metadata defining the structure of each object, the Mapper displays the structure of the source and target objects and allows us to visually map the fields with a few mouse clicks. A new object is instantiated with the values from the input object. We use the job object's generateGUID method to generate a unique Correlation Identifier and assign it to the Correlation ID field of the bankRequest object. As we will see later, the Correlation ID field is important to allow us to process more than one loan request at the same time.
视觉映射器任务创建银行请求消息
The Visual Mapper Task Creates the Bank Request Message
我们可以使用 ECMAScript 任务来完成相同的功能,而不是使用可视映射器。等效代码如下所示。您可以看到我们如何创建 BankQuoteRequest 类型的新对象并分配源对象中的每个字段:
Instead of using the visual Mapper, we could have used an ECMAScript task to accomplish the same function. The equivalent code would look like the following. You can see how we create a new object of type BankQuoteRequest and assign each field from the source objects:
var Bank = new aeclass.BankQuoteRequest(); //创建ID来唯一标识本次交易。 // 稍后我们将需要它来过滤回复 银行.CorrelationID = job.generateGUID(); 银行.SSN = 工作.请求.SSN; 银行.CreditScore = job.creditReply.CreditScore; 银行.HistoryLength = job.creditReply.HistoryLength; 银行.LoanAmount = job.request.LoanAmount; 银行.LoanTerm = job.request.LoanTerm; job.bankRequest = 银行;
var bank = new aeclass.BankQuoteRequest(); //Create ID to uniquely identify this transaction. // We will need this later to filter replies bank.CorrelationID = job.generateGUID(); bank.SSN = job.request.SSN; bank.CreditScore = job.creditReply.CreditScore; bank.HistoryLength = job.creditReply.HistoryLength; bank.LoanAmount = job.request.LoanAmount; bank.LoanTerm = job.request.LoanTerm; job.bankRequest = bank;
是否使用可视化Mapper任务或 ECMAScript 任务来实现消息转换器功能取决于映射的类型和复杂性。Mapper任务可以为我们提供源对象和目标对象之间连接的良好视觉视图,但当对象具有许多字段时,可能会变得难以阅读。另一方面,映射器包含特殊函数,允许我们用一行映射重复字段(即数组),而不必编写循环代码。
Whether to use the visual Mapper task or an ECMAScript task to implement a Message Translator function depends on the type and complexity of a mapping. The Mapper task can give us a nice visual view of the connections between source and target objects but can become hard to read when the objects have many fields. On the other hand, the Mapper includes special functions that allow us to map repeating fields (i.e., Arrays) with a single line instead of having to code a loop.
接下来,一个非常简单的 ECMAScript 任务实例化一个出价数组,该数组将保存传入的银行响应。该脚本仅包含一行:
Next, a very simple ECMAScript task instantiates a bid array that will hold the incoming bank responses. This script contains only a single line:
job.bids = new Array();
job.bids = new Array();
下一个任务是 Signal Out 任务,它将bankRequest对象发布到bank.loan.request通道。这是一个异步操作,因此该过程立即转换到下一步,而无需等待回复。
The next task is a Signal Out task that publishes the bankRequest object to the bank.loan.request channel. This is an asynchronous action, so the process immediately transitions to the next step without waiting for a reply.
以下任务等待bank.loan 上传入的报价回复消息。回复频道。如果在指定的超时间隔内收到消息,脚本任务会将收到的消息添加到 bids 数组中:
The following tasks waits for incoming quote reply messages on the bank.loan. reply channel. If a message is received within the specified timeout interval, the script task adds the received message to the bids array:
job.bids[job.bids.length] = job.loanReply;
job.bids[job.bids.length] = job.loanReply;
拍卖期结束后,信号输入任务超时并将流程转换到最终任务。此 ECMAScript 任务实现 Aggregator 的聚合算法,从所有出价中选择最佳报价。该代码创建 LoanQuoteReply 的新实例,将SSN和LoanAmount字段从请求对象传输到此回复对象,并循环遍历bids数组以查找最佳报价。
Once the auction period ends, the Signal In task times out and transitions the process to the final task. This ECMAScript task implements the aggregation algorithm of the Aggregator , selecting the best quote from all the bids. The code creates a new instance of the LoanQuoteReply, transfers the SSN and LoanAmount fields from the request object to this reply object, and loops through the bids array to find the best quote.
var LoanReply = new aeclass.LoanQuoteReply();
LoanReply.SSN = job.request.SSN;
LoanReply.LoanAmount = job.request.LoanAmount;
var bids = job.bids;
for(var i = 0; i < bids.length; i++){
var item = bids[i];
if(i == 0 || (item.InterestRate <loanReply.InterestRate)){
LoanReply.InterestRate = item.InterestRate;
LoanReply.QuoteID = item.QuoteID;
}
}
job.loanReply=loanReply;
var loanReply = new aeclass.LoanQuoteReply();
loanReply.SSN = job.request.SSN;
loanReply.LoanAmount = job.request.LoanAmount;
var bids = job.bids;
for(var i = 0; i < bids.length; i++){
var item = bids[i];
if(i == 0 || (item.InterestRate < loanReply.InterestRate)){
loanReply.InterestRate = item.InterestRate;
loanReply.QuoteID = item.QuoteID;
}
}
job.loanReply = loanReply;
ECMA 脚本的最后一行将要返回给客户的贷款回复对象放入作业槽中。我们将贷款经纪人作业创建者配置为将作业的LoanReply属性作为回复消息返回给客户(见图)。当该过程完成时,作业创建者会拉取在作业的LoanReply属性中找到的消息,并将该值返回给客户端。
The last line of the ECMA script places the loan reply object to be returned to customer into a job slot. We configured the loan broker job creator to return the loanReply attribute of the job to the customer as the reply message (see figure). When the process finishes, the job creator pulls the message found in the loanReply attribute of the job, and returns the value to the client.
配置 Job Creator 的规则集以返回最佳报价
Configuring the Job Creator's Rule Set to Return the Best Quote
拍卖的实施带来了一些发展障碍。并发性在一定程度上增加了问题的复杂性。为了使拍卖正常进行,我们需要发布异步消息,然后等待指定的时间以获得回复。但是,如果同时发生多个拍卖,我们必须确保每个贷款经纪人只收到其关注的回复,而不是所有回复。
The implementation of the auction provided a few development hurdles. Concurrency adds a degree of complexity to the problem. For the auction to work, we need to publish an asynchronous message, then wait a specified amount of time for replies. However, if multiple auctions occur at once, we must ensure that each loan broker receives only the replies it is concerned about, not all the replies.
此功能是使用相关标识符和选择性消费者来实现的丢弃不相关的消息。理想情况下,我们希望这种过滤发生在通道级别,而不是流程实例。然而,这需要在运行时动态创建通道主题,每个通道主题对应一个待处理的拍卖。目前,TIB/IntegrationManager 中存在实现限制,阻止我们使用此方法。当流程图在运行时实例化时,它需要注册它正在侦听的所有主题。这允许流程引擎侦听有关这些主题的任何消息并根据需要对它们进行排队。排队可以防止由于流程图在一个主题上发布然后转换到另一状态以侦听答复而导致的计时错误。在异步世界中,如果转换时间太长,在流程图有机会订阅之前可以收到回复。因此,流程图可以方便地对入站消息进行排队,但代价是不允许动态主题。考虑到本示例的要求,过程级别的过滤效果非常好。这相关标识符是分配给每个流程的唯一标识符,然后传递给每个银行流程并包含在每个回复中。单行 ECMAScript 在Signal In任务中提供过滤:
This functionality was implemented using a Correlation Identifier and a Selective Consumer to discard unrelated messages. Ideally, we would want this filtering to occur at the channel level and not at the process instance. However, that would require the dynamic creation of channel subjects at runtime, one for each pending auction. Presently, there are implementation constraints within TIB/IntegrationManager that prevent us from using this approach. When a process diagram is instantiated at runtime, it needs to register all subjects it is listening to. This allows the process engine to listen for any messages on those subjects and queue them as needed. Queuing prevents timing bugs due to process diagrams that publish on one subject and then transition to another state to listen for a reply. In an asynchronous world, if the transition takes too long, the reply could be received before the process diagram gets a chance to subscribe. Therefore, the process diagram conveniently queues inbound messages, but at the cost of disallowing dynamic subjects. Given the requirements of the present example, filtering at the process level works perfectly well. The Correlation Identifier is a unique identifier assigned to each process that is then passed to each bank process and is included in each reply. A single line of ECMAScript provides the filtering in the Signal In task:
(event.msg.CorrelationID == job.bankRequest.CorrelationID)
(event.msg.CorrelationID == job.bankRequest.CorrelationID)
示例就绪后,我们现在可以运行该解决方案。运行该解决方案涉及启动 TIB/IntegrationManager 引擎。为了测试该解决方案,存储库中包含一个简单的测试客户端,该客户端每 5 秒提交一次贷款请求。简单的存根用于信贷服务和银行的实施。发布订阅通道的优点之一就是我们可以方便地监听消息总线上的所有消息来检查消息流。我们使用一个简单的工具将所有消息记录到控制台。为了清楚起见,我们截断了消息内容以仅显示相关字段,并消除了每条消息中包含的格式标识和跟踪信息。从日志中,我们可以看到消息的时间、主题和收件箱。收件箱是同步服务的返回地址。
With the example in place, we can now run the solution. Running the solution involves starting the TIB/IntegrationManager engine. To test the solution, a simple test client is included in the repository that submits loan requests every 5 seconds. Simple stubs are used for the implementations of the credit service and the banks. One of the advantages of Publish-Subscribe Channels is that we can easily listen to all messages on the message bus to inspect the message flow. We used a simple tool to log all messages to the console. For clarity, we truncated the message content to show only relevant fields and eliminated the format identification and tracking information included in each message. From the logs, we can see the times, subjects, and inboxes of messages. The inboxes are the return addresses for the synchronous services.
tibrvlisten:听主题 > 2003-07-12 16:42:30 (2003-07-12 21:42:30.032000000Z): 主题=客户.贷款.请求, 回复=_INBOX.C0A80164.1743F10809898B4B60.3, SSN=1234567890 贷款金额=100000.000000 贷款期限=360 2003-07-12 16:42:30 (2003-07-12 21:42:30.052000000Z): 主题=credit.loan.request, 回复=_INBOX.C0A80164.1743F10809898B4B60.4, SSN=1234567890 2003-07-12 16:42:30 (2003-07-12 21:42:30.092000000Z): 主题=银行.贷款.请求, SSN=1234567890 信用评分=345 历史长度=456 贷款金额=100000.000000 CorrelationID="pUQI3GEWK5Q3d-QiuLzzwGM-zzw" 贷款期限=360 2003-07-12 16:42:30 (2003-07-12 21:42:30.112000000Z): 主题=银行.贷款.回复, InterestRate=5.017751 QuoteID="5E0x1K_dK5Q3i-QiuMzzwGM-zzw" 错误代码=0CorrelationID="pUQI3GEWK5Q3d-QiuLzzwGM-zzw" 2003-07-12 16:42:30 (2003-07-12 21:42:30.112000000Z): 主题=银行.贷款.回复, InterestRate=5.897514 QuoteID="S9iIAXqgK5Q3n-QiuNzzwGM-zzw" 错误代码=0
CorrelationID="pUQI3GEWK5Q3d-QiuLzzwGM-zzw"
tibrvlisten: Listening to subject > 2003-07-12 16:42:30 (2003-07-12 21:42:30.032000000Z): subject=customer.loan.request, reply=_INBOX.C0A80164.1743F10809898B4B60.3, SSN=1234567890 LoanAmount=100000.000000 LoanTerm=360 2003-07-12 16:42:30 (2003-07-12 21:42:30.052000000Z): subject=credit.loan.request, reply=_INBOX.C0A80164.1743F10809898B4B60.4, SSN=1234567890 2003-07-12 16:42:30 (2003-07-12 21:42:30.092000000Z): subject=bank.loan.request, SSN=1234567890 CreditScore=345 HistoryLength=456 LoanAmount=100000.000000 CorrelationID="pUQI3GEWK5Q3d-QiuLzzwGM-zzw" LoanTerm=360 2003-07-12 16:42:30 (2003-07-12 21:42:30.112000000Z): subject=bank.loan.reply, InterestRate=5.017751 QuoteID="5E0x1K_dK5Q3i-QiuMzzwGM-zzw" ErrorCode=0 CorrelationID="pUQI3GEWK5Q3d-QiuLzzwGM-zzw" 2003-07-12 16:42:30 (2003-07-12 21:42:30.112000000Z): subject=bank.loan.reply, InterestRate=5.897514 QuoteID="S9iIAXqgK5Q3n-QiuNzzwGM-zzw" ErrorCode=0 CorrelationID="pUQI3GEWK5Q3d-QiuLzzwGM-zzw"
我们在通道customer.loan.request 上看到来自测试客户端的请求消息。该消息包括客户的社会安全号码以及所需的贷款金额和期限(360 个月 100,000 美元)。该消息指定测试客户端的私人回复通道_INBOX.C0A80164 。1743F10809898B4B60.3,作为返回地址,以便贷款经纪人知道将回复消息发送到哪里。
We see the request message from the test client on the channel customer.loan.request. The message includes the social security number of the customer as well as the desired loan amount and term ($100,000 for 360 months). The message specifies the test client's private reply channel, _INBOX.C0A80164. 1743F10809898B4B60.3, as the Return Address so that the loan broker knows where to send the reply message.
下一条消息表示从贷款经纪人到信贷服务的请求,携带贷款经纪人的私人收件箱作为返回地址。信用服务只需要社会安全号码。我们的日志记录工具不会捕获回复消息,因为_INBOX频道是私有频道。
The next message represents the request from the loan broker to the credit service, carrying the private inbox of the loan broker as the Return Address. The credit service requires only the social security number. The reply message is not captured by our logging tool because _INBOX channels are private channels.
贷款经纪人收到信用评分后,会向bank.loan.request通道发布一条消息。我们示例中的两家银行存根立即回复各一个利率。每家银行还会为回复分配一个唯一的QuoteID,以便客户稍后可以参考。拍卖时间将在几秒钟内超时。由于我们看不到贷款经纪人进程对测试客户端的回复消息,因此我们可以查看进程管理器引擎的调试日志,以验证我们的测试客户端是否收到了较低的利率。在此消息中,我们可以看到该类的CorrelationID和格式指示符。
After the loan broker receives the credit score, it publishes a message to the bank.loan.request channel. The two bank stubs in our example immediately reply with one interest rate each. Each bank also assigns a unique QuoteID to the reply so that the customer can refer back to it later. The auction period times out in a few seconds. Because we cannot see the reply message from the loan broker process to the test client, we can look in the debug log for the process manager engine to verify that our test client received the lower interest rate. Within this message we can see the CorrelationID and the Format Indicator for the class.
回复=类/LoanQuoteReply {
SSN=1234567890
利率=5.017751017038945
贷款金额=100000.0
QuoteID=5E0x1K_dK5Q3i-QiuMzzwGM-zzw
}
reply= class/LoanQuoteReply {
SSN=1234567890
InterestRate=5.017751017038945
LoanAmount=100000.0
QuoteID=5E0x1K_dK5Q3i-QiuMzzwGM-zzw
}
该解决方案提供了一些有趣的比较点。可视化工作流程很容易查看和理解,但就像在代码中一样,随着实现变得更加复杂,这种相对简单的情况可能会变成混乱的迷宫。Scatter-Gather的动态特性允许发布-订阅接口的彻底解耦。贷款经纪人可以在不了解认购银行的情况下发布银行贷款请求。同样,回复聚合器不需要知道预期回复的数量。这种灵活性的潜在缺点是它可能隐藏由于不正确的主题命名、开发人员错误或丢失的消息而导致的错误。
The solution provides some interesting points for comparison. Visual workflows can be easy to view and understand, but just as in code, this relative simplicity can turn into a maze of confusion as implementations become more complex. The dynamic nature of the Scatter-Gather allows for a clean decoupling of the publish-subscribe interface. The loan broker can publish bank loan requests without any knowledge of the subscribing banks. Similarly, the Aggregator for the replies doesn't need to know the number of expected replies. The potential downside of this flexibility is that it can hide errors resulting from incorrect subject naming, developer error, or dropped messages.
TIBCO 的可视化、GUI 驱动的实现为代码级实现提供了一种必然的方法。虽然代码的强大功能和灵活性无可争议,但更加图形化的环境可以极大地简化非常复杂的任务。配置是集成的一个非常重要的部分,图形环境非常适合这一点。有时,开发人员似乎必须在编写代码和使用供应商开发工具之间做出选择。然而,它们通常可以有效共存。例如,您可以选择使用 TIB/IntegrationManager 来建模集成工作流并使用 J2EE 会话 bean 来实现域逻辑。
The visual, GUI-driven implementation with TIBCO provides a corollary approach to the code-level implementations. While the sheer power and flexibility of code cannot be argued against, a more graphical environment can greatly simplify very complex tasks. Configuration is a very large part of integration, and graphical environments work well for this. At times, it may seem that developers have to choose between writing code and using a vendor development tool. However, they can often coexist effectively. For instance, you could choose to use TIB/IntegrationManager to model integration workflows and use J2EE session beans to implement domain logic.
为了简洁起见,我们的示例相对简单,导致了一个易于理解的场景。现实世界的实现很少如此简单。快速开发工具可以促进更快的初始开发,但可能会限制您的开发选择。流程管理工具可以帮助隐藏消息传递的复杂性,使开发人员能够专注于集成任务,而不必担心幕后发生的事情。然而,对消息传递基础设施的忽视导致了不止一个项目的消亡。
For the sake of brevity, our example was relatively simple, leading to an easy-to-understand scenario. Real-world implementations are rarely this simple. Rapid development tools can facilitate faster initial development but can limit your development options. Process management tools can help hide the complexities of messaging, allowing developers to focus on integration tasks without worrying about what happens behind the scenes. However, ignorance of messaging infrastructure has led to the death of more than one project.
在第 3 章“消息系统”中,我们讨论了消息端点。这就是应用程序连接到消息传递系统以便发送和接收消息的方式。作为应用程序程序员,当您对 JMS 或System.Messaging命名空间等消息传递 API 进行编程时,您正在开发端点代码。如果您使用的是商业中间件包,则大部分编码已经通过供应商提供的库和工具为您完成。
In Chapter 3, "Messaging Systems," we discussed Message Endpoint. This is how an application connects to a messaging system so that it can send and receive messages. As an application programmer, when you program to a messaging API such as JMS or the System.Messaging namespace, you're developing endpoint code. If you are using a commercial middleware package, most of this coding is already done for you through the libraries and tools provided by the vendor.
某些端点模式适用于发送方和接收方。它们关注应用程序与消息传递系统的总体关系。
Some endpoint patterns apply to both senders and receivers. They concern how the application relates to the messaging system in general.
封装消息传递代码 一般来说,应用程序不应该知道它正在使用消息传递来与其他应用程序集成。大多数应用程序的代码在编写时都应该不考虑消息传递。在应用程序与其他应用程序集成的地方,应该有一个薄层代码来执行应用程序的集成部分。当通过消息传递实现集成时,将应用程序附加到消息传递系统的那层薄代码就是消息传递网关。
数据转换 当发送方和接收方应用程序使用相同的内部数据表示形式并且消息格式也使用相同的表示形式时,这会很棒。然而,情况往往并非如此。发送者和接收者在数据格式上不一致,或者消息使用不同的格式(通常是为了支持其他发送者和接收者)。在这种情况下,请使用消息传递映射器在应用程序格式和消息格式之间转换数据。
外部控制的事务 消息系统在内部使用事务;在外部,默认情况下,每个发送或接收方法调用都在其自己的事务中运行。但是,消息生产者和消费者可以选择使用事务客户端在外部控制这些事务,这在您需要将多个消息批处理在一起或与其他事务服务协调消息传递时非常有用。
Encapsulate the messaging code In general, an application should not be aware that it is using Messaging to integrate with other applications. Most of the application's code should be written without messaging in mind. At the points where the application integrates with others, there should be a thin layer of code that performs the application's part of the integration. When the integration is implemented with messaging, that thin layer of code that attaches the application to the messaging system is a Messaging Gateway.
Data translation It's great when the sender and receiver applications use the same internal data representation and when the message format uses that same representation as well. However, this is often not the case. Either the sender and receiver disagree on data format, or the messages use a different format (usually to support other senders and receivers). In this situation, use a Messaging Mapper to convert data between the application's format and the message's format.
Externally controlled transactions Messaging systems use transactions internally; externally, by default, each send or receive method call runs in its own transaction. However, message producers and consumers have the option of using a Transactional Client to control these transactions externally, which is useful when you need to batch together multiple messages or to coordinate messaging with other transactional services.
其他端点模式仅适用于消息使用者。发送消息很容易。在决定何时发送消息、消息应包含什么内容以及如何将其意图传达给接收者时,会涉及到一些问题,这就是我们使用消息构造模式的原因(请参阅第 5 章“消息构造”),但是消息一旦构建,发送很容易。另一方面,接收消息则很棘手。因此,许多端点模式都是关于接收消息的。
Other endpoint patterns apply only to message consumers. Sending messages is easy. There are issues involved in deciding when a message should be sent, what it should contain, and how to communicate its intent to the receiverthat's why we have the Message Construction patterns (see Chapter 5, "Message Construction")but once the message is built, sending it is easy. Receiving messages, on the other handthat's tricky. Therefore, many endpoint patterns are about receiving messages.
消息消费中最重要的主题是限制:应用程序控制或限制其消费消息的速率的能力。正如本书的简介中所讨论的,任何服务器面临的一个潜在问题是大量的客户端请求可能会使服务器过载。使用远程过程调用,服务器几乎受客户端调用速率的影响。同样,对于消息传递,服务器无法控制客户端发送请求的速率,但服务器可以控制处理这些请求的速率。应用程序不必像消息传递系统传送消息那样快地接收和处理消息;它可以以可持续的速度处理它们,而消息通道以先到先服务的方式对要处理的消息进行排队。但是,如果消息堆积太多并且服务器有资源更快地处理更多消息,则服务器可以使用并发消息使用者来增加其消息消费吞吐量。因此,使用这些消息消费者模式可以让您的应用程序控制其消费消息的速率。
An overriding theme in message consumption is throttling: the ability of an application to control, or throttle, the rate at which it consumes messages. As discussed in the book's Introduction, a potential problem any server faces is that a high volume of client requests could overload the server. With Remote Procedure Invocation, the server is pretty much at the mercy of the rate that clients make calls. Likewise, with Messaging, the server cannot control the rate at which clients send requestsbut the server can control the rate at which it processes those requests. The application does not have to receive and process the messages as rapidly as they're delivered by the messaging system; it can process them at a sustainable pace while the Message Channel queues up the messages to be processed in a first come, first served basis. However, if the messages are piling up too much and the server has the resources to process more messages faster, the server can increase its message consumption throughput using concurrent message consumers. So use these message consumer patterns to let your application control the rate at which it consumes messages.
许多消息使用者模式成对出现,代表替代方案,这意味着您可以以一种或另一种方式设计端点。单个应用程序可以以一种方式设计某些端点,以另一种方式设计某些端点,但单个端点只能实现一种替代方案。每一对的替代方案可以组合起来,从而为如何实现特定端点提供大量选择。
Many of the message consumer patterns come in pairs that represent alternatives, meaning that you can design an endpoint one way or the other. A single application may design some endpoints one way and some endpoints the other way, but a single endpoint can implement only one alternative. Alternatives from each pair can be combined, leading to a great number of choices for how to implement a particular endpoint.
同步或异步消费者 一种选择是使用轮询消费者还是事件驱动消费者 [JMS 1.1 ]、[ Hapner ]、[ Dickman ] 。轮询提供了最好的限制,因为如果服务器繁忙,它不会请求更多消息,因此消息将排队。事件驱动的消费者倾向于在消息到达时尽快处理消息,这可能会使服务器过载;但每个消费者一次只能处理一条消息,因此限制消费者的数量可以有效地限制消费速度。
消息分配与消息抓取 另一种选择涉及少数消费者如何处理少数消息。如果每个消费者收到一条消息,他们可以同时处理这些消息。最简单的方法是竞争消费者,其中一个点对点通道有多个消费者。每个人都可能抓取任何消息;消息系统的实现决定了哪个消费者接收消息。如果您想控制此消息到消费者的匹配过程,请使用消息调度程序。这是一个接收消息但将其委托给执行者进行处理的单个消费者。应用程序可以通过限制消费者/执行者的数量来限制消息负载。此外,消息调度程序中的调度程序可以实现显式限制行为。
接受所有消息或过滤器 默认情况下,消息通道上传递的任何消息都可供侦听该通道的任何消息端点使用以供消息使用。然而,一些消费者可能不想只消费该通道上的任何消息,而是希望只消费某种类型或描述的消息。这种有区别的消费者可以使用选择性消费者来描述它愿意接收哪种消息。然后,消息传递系统将仅向该接收者提供与该描述匹配的消息。
断开连接时订阅 发布-订阅通道出现的一个 问题是,如果订阅者对在特定通道上发布的数据感兴趣并且将再次感兴趣,但当前与网络断开连接或关闭以进行维护,该怎么办?断开连接的应用程序是否会错过断开连接时发布的消息,即使它已订阅?默认情况下,是的,订阅仅在订阅者连接时有效。为了防止应用程序丢失连接之间发布的消息,请将其设为持久订阅者。
幂等性 有时,同一条消息会被多次传递,要么是因为消息传递系统不确定消息是否已成功传递,要么是因为消息通道的服务质量已降低以提高性能。另一方面,消息接收者倾向于假设每条消息只会被传递一次,并且在由于重复消息而重复处理时往往会导致问题。设计为幂等接收器的接收器可以优雅地处理重复消息,并防止它们在接收器应用程序中引起问题。
同步或异步服务 另一个艰难的选择是应用程序是否应该公开其服务以同步(通过远程过程调用)或异步(通过消息传递)调用。不同的客户可能喜欢不同的方法;不同的情况可能需要不同的方法。由于通常很难选择一种方法或另一种方法,所以让我们同时采用两种方法。服务激活器连接消息通道到应用程序中的同步服务,以便在收到消息时调用该服务。同步客户端可以直接调用服务;异步客户端可以通过发送消息来调用服务。
Synchronous or asynchronous consumer One alternative is whether to use a Polling Consumer or an Event-Driven Consumer [JMS 1.1], [Hapner], [Dickman]. Polling provides the best throttling because if the server is busy, it won't ask for more messages, so the messages will queue up. Consumers that are event-driven tend to process messages as fast as they arrive, which could overload the server; but each consumer can only process one message at a time, so limiting the number of consumers effectively throttles the consumption rate.
Message assignment versus message grab Another alternative concerns how a handful of consumers process a handful of messages. If each consumer gets a message, they can process the messages concurrently. The simplest approach is Competing Consumers, where one Point-to-Point Channel has multiple consumers. Each one could potentially grab any message; the messaging system's implementation decides which consumer gets a message. If you want to control this message-to-consumer matching process, use a Message Dispatcher. This is a single consumer that receives a message but delegates it to a performer for processing. An application can throttle message load by limiting the number of consumers/performers. Also, the dispatcher in a Message Dispatcher can implement explicit throttling behavior.
Accept all messages or filter By default, any message delivered on a Message Channel becomes available to any Message Endpoint listening on that channel for messages to consume. However, some consumers may not want to consume just any message on that channel, but wish to consume only messages of a certain type or description. Such a discriminating consumer can use a Selective Consumer to describe what sort of message it's willing to receive. Then the messaging system will make only messages matching that description available to that receiver.
Subscribe while disconnected An issue that comes up with Publish-Subscribe Channels is, What if a subscriber was interested in the data being published on a particular channel and will be again, but is currently disconnected from the network or shut down for maintenance? Will a disconnected application miss messages published while it is disconnected, even though it has subscribed? By default, yes, a subscription is only valid while the subscriber is connected. To keep the application from missing messages published between connections, make it a Durable Subscriber.
Idempotency Sometimes the same message gets delivered more than once, either because the messaging system is not certain the message has been successfully delivered yet, or because the Message Channel's quality-of-service has been lowered to improve performance. Message receivers, on the other hand, tend to assume that each message will be delivered exactly once, and they tend to cause problems when they repeat processing because of repeat messages. A receiver designed as an Idempotent Receiver handles duplicate messages gracefully and prevents them from causing problems in the receiver application.
Synchronous or asynchronous service Another tough choice is whether an application should expose its services to be invoked synchronously (via Remote Procedure Invocation ) or asynchronously (via Messaging ). Different clients may prefer different approaches; different circumstances may require different approaches. Since it's often hard to choose just one approach or the other, let's have both. A Service Activator connects a Message Channel to a synchronous service in an application so that when a message is received, the service is invoked. Synchronous clients can simply invoke the service directly; asynchronous clients can invoke the service by sending a message.
本章的另一个重要主题是将事务客户端与其他模式一起使用的困难。事件驱动的消费者通常无法从外部正确控制事务,消息调度程序必须仔细设计才能做到这一点,并且外部管理事务的竞争消费者可能会遇到严重的问题。使用Transactional Client 的最安全选择是使用单个Polling Consumer ,但这可能不是一个非常令人满意的解决方案。
Another significant theme in this chapter is difficulty using Transactional Client with other patterns. Event-Driven Consumer usually cannot externally control transactions properly, Message Dispatcher must be carefully designed to do so, and Competing Consumers that externally manage transactions can run into significant problems. The safest bet for using Transactional Client is with a single Polling Consumer, but that may not be a very satisfactory solution.
特别值得一提的是 JMS 风格的消息驱动 Bean (MDB),它是 Enterprise JavaBean (EJB) 的一种类型 [ EJB 2.0 ]、[ Hapner ]。MDB 是一个消息使用者,它既是事件驱动使用者又是支持 J2EE 分布式(例如XAResource )事务客户端,并且它可以动态地汇集为竞争使用者,甚至对于发布-订阅通道也是如此。。在自己的应用程序代码中实现这种组合既困难又乏味,但此功能是作为兼容 EJB 容器(例如 BEA 的 WebLogic 和 IBM 的 WebSphere)的现成功能提供的。(MDB 框架是如何实现的?本质上,容器实现了一个具有动态大小的可重用执行者池的消息调度程序,其中每个执行者使用自己的会话和事务来消费消息本身。)
Special mention should be made of JMS-style message-driven beans (MDBs), one type of Enterprise JavaBeans (EJB) [EJB 2.0], [Hapner]. An MDB is a message consumer that is both an Event-Driven Consumer and a Transactional Client that supports J2EE distributed (e.g., XAResource) transactions, and it can be dynamically pooled as Competing Consumers, even for a Publish-Subscribe Channel. This is a difficult and tedious combination to implement in one's own application code, but this functionality is provided as a ready-built feature of compatible EJB containers (such as BEA's WebLogic and IBM's WebSphere). (How is the MDB framework implemented? Essentially, the container implements a Message Dispatcher with a dynamically sized pool of reusable performers, where each performer consumes the message itself using its own session and transaction.)
最后,请记住,单个消息端点很可能组合本章中的几种不同模式。一组竞争消费者可以被实现为轮询消费者,它们也是选择性消费者并充当应用程序中服务的服务激活器。消息调度程序可以是事件驱动的消费者和使用消息映射器的持久订阅者。无论端点实现什么其他模式,它也应该是一个消息传递网关。因此,不要考虑使用哪一种模式,而是考虑组合。这就是用模式解决问题的美妙之处。
Finally, keep in mind that a single Message Endpoint may well combine several different patterns from this chapter. A group of Competing Consumers may be implemented as Polling Consumers that are also Selective Consumers and act as a Service Activator on a service in the application. A Message Dispatcher may be an Event-Driven Consumer and a Durable Subscriber that uses a Messaging Mapper. Whatever other patterns an endpoint implements, it should also be a Messaging Gateway. So, don't think of what one pattern to usethink of the combinations. That's the beauty of solving the problems with patterns.
有很多选项可以使应用程序成为消息端点。本章解释了这些选项是什么以及如何充分利用它们。
There are a lot of options for making an application into a Message Endpoint. This chapter explains what those options are and how to make the best use of them.
应用程序通过消息传递访问另一个系统。
An application accesses another system via Messaging.
|
如何封装应用程序其余部分对消息传递系统的访问? How do you encapsulate access to the messaging system from the rest of the application? |
大多数自定义应用程序通过供应商提供的 API 访问消息传递基础设施。虽然此类 API 有许多不同的风格,但这些库通常公开类似的功能,例如“开放通道”、“创建消息”和“发送消息”。虽然这种类型的 API 允许应用程序通过任何类型的通道发送任何类型的消息数据,但有时很难判断发送消息数据的意图是什么。
Most custom applications access the messaging infrastructures through a vendor-supplied API. While there are many different flavors of such APIs, these libraries generally expose similar functions, such as "open channel," "create message," and "send message." While this type of API allows the application to send any kind of message data across any kind of channel, it is sometimes hard to tell what the intent of sending the message data is.
消息传递解决方案本质上是异步的。这可能会使通过消息传递访问外部函数的代码变得复杂。应用程序必须发送请求消息并期望回复消息稍后到达(请参阅Request-Reply ),而不是调用返回数字信用评分的方法GetCreditScore。应用程序开发人员可能更喜欢同步函数的简单语义,而不是处理传入的消息事件。
Messaging solutions are inherently asynchronous. This can complicate the code to access an external function over messaging. Instead of calling a method GetCreditScore that returns the numeric credit score, the application has to send the request message and expect the reply message to arrive at a later time (see Request-Reply ). The application developer may prefer the simple semantics of a synchronous function to dealing with incoming message events.
应用程序之间的松散耦合提供了体系结构优势,例如对消息格式的微小变化(即添加字段)的恢复能力。通常,松耦合是通过使用 XML 文档或其他不像 Java 或 C# 类那样强类型的数据结构来实现的。针对此类结构进行编码既乏味又容易出错,因为没有编译类型支持来检测拼写错误的字段名称或不匹配的数据类型。因此,我们常常以牺牲应用程序开发工作量为代价来获得数据格式的灵活性。
Loose coupling between applications provides architectural advantages, such as resilience to minor changes in message formats (i.e., adding fields). Usually, the loose coupling is achieved by using XML documents or other data structures that are not strongly typed like a Java or C# class. Coding against such structures is tedious and error-prone because there is no compile-type support to detect misspelled field names or mismatched datatypes. Therefore, we often gain the flexibility in data formats at the expense of application development effort.
有时,通过消息传递执行的简单逻辑功能需要发送多个消息。例如,获取客户信息的功能实际上可能需要多条消息,一条消息用于获取地址,另一条消息用于获取订单历史记录,另一条消息用于获取个人信息。这些消息中的每一个都可以由不同的系统处理。我们不想让应用程序代码与发送和接收三个单独消息所需的所有逻辑混淆。我们可以通过使用Scatter-Gather来减轻应用程序的一些负担它接收一条消息,发送三个单独的消息,并将它们聚合回一条回复消息。然而,我们并不总是有能力将此功能添加到消息传递中间件中。
Sometimes, a simple logical function to be executed via messaging requires more than one message to be sent. For example, a function to get customer information may in reality require multiple messages, one to get the address, another to get the order history, and yet another to get personal information. Each of these messages may be processed by a different system. We would not want to clutter the application code with all the logic required to send and receive three separate messages. We could take some of the burden off the application by using a Scatter-Gather that receives a single message, sends three separate messages, and aggregates them back into a single reply message. However, not always do we have the luxury of adding this function to the messaging middleware.
|
使用消息传递网关,这是一个包装特定于消息传递的方法调用并向应用程序公开特定于域的方法的类。 Use a Messaging Gateway, a class that wraps messaging-specific method calls and exposes domain-specific methods to the application. |
消息传送网关封装消息传送特定的代码(例如,发送或接收消息所需的代码)并将其与应用程序代码的其余部分分开。这样,只有消息网关代码知道消息系统;其余的应用程序代码则不然。消息传递网关向应用程序的其余部分公开业务功能,以便不需要应用程序设置诸如Message.MessageReadPropertyFilter.AppSpecific 之类的属性,消息传递网关公开有意义的方法(例如GetCreditScore ,这些方法像任何其他方法一样接受强类型参数。消息传递网关是更通用的网关模式[ EAA]的消息传递特定版本。
The Messaging Gateway encapsulates messaging-specific code (e.g., the code required to send or receive a message) and separates it from the rest of the application code. This way, only the Messaging Gateway code knows about the messaging system; the rest of the application code does not. The Messaging Gateway exposes a business function to the rest of the application so that instead of requiring the application to set properties like Message.MessageReadPropertyFilter.AppSpecific, a Messaging Gateway exposes meaningful methods such as GetCreditScore that accept strongly typed parameters just like any other method. A Messaging Gateway is a messaging-specific version of the more general Gateway pattern [EAA].
网关消除了应用程序和消息系统之间的直接依赖性
A Gateway Eliminates Direct Dependencies between the Application and the Messaging Systems
消息传递网关位于应用程序和消息传递系统之间,并向应用程序提供特定于域的 API(请参见上图)。因为应用程序甚至不知道它正在使用消息传递系统,所以我们可以将网关替换为使用另一种集成技术(例如远程过程调用或 Web 服务)的不同实现。
A Messaging Gateway sits between the application and the messaging system and provides a domain-specific API to the application (see previous figure). Because the application doesn't even know that it's using a messaging system, we can swap out the gateway with a different implementation that uses another integration technology, such as remote procedure calls or Web services.
许多消息网关将消息发送到另一个组件并期望回复消息(请参阅请求-回复) 。这样的消息传递网关可以通过两种不同的方式实现:
Many Messaging Gateways send a message to another component and expect a reply message (see Request-Reply ). Such a Messaging Gateway can be implemented in two different ways:
阻塞(同步)消息传递网关
Blocking (Synchronous) Messaging Gateway
事件驱动(异步)消息传递网关
Event-Driven (Asynchronous) Messaging Gateway
阻塞消息网关发送消息并等待回复消息到达,然后将控制权返回给应用程序。当网关收到回复时,它会处理消息并将结果返回给应用程序(请参见以下序列图)。
A blocking Messaging Gateway sends out a message and waits for the reply message to arrive before returning control to the application. When the gateway receives the reply, it processes the message and returns the result to the application (see the following sequence diagram).
阻塞(同步)消息传递网关
Blocking (Synchronous) Messaging Gateway
阻塞消息传递网关封装了消息传递交互的异步性质,向应用程序逻辑公开常规同步方法。因此,应用程序不知道通信中的任何异步性。例如,阻塞网关可能会公开以下方法:
A blocking Messaging Gateway encapsulates the asynchronous nature of the messaging interaction, exposing a regular synchronous method to the application logic. Thus, the application is unaware of any asynchronicity in the communication. For example, a blocking gateway may expose the following method:
int GetCreditScore(字符串 SSN);
int GetCreditScore(string SSN);
虽然这种方法使得针对消息网关编写应用程序代码变得非常简单,但它也可能导致性能不佳,因为应用程序最终将大部分时间花在等待回复消息上,而它可能正在执行其他任务。
While this approach makes writing application code against the Messaging Gateway very simple, it can also lead to poor performance because the application ends up spending most of its time sitting around and waiting for reply messages while it could be performing other tasks.
事件驱动的消息传递网关向应用程序公开消息传递层的异步特性。当应用程序向消息传递网关发出特定于域的请求时,它会提供特定于域的回调来进行回复。控制立即返回到应用程序。当回复消息到达时,消息网关对其进行处理,然后调用回调(请参见以下序列图)。
An event-driven Messaging Gateway exposes the asynchronous nature of the messaging layer to the application. When the application makes the domain-specific request to the Messaging Gateway, it provides a domain-specific callback for the reply. Control returns immediately to the application. When the reply message arrives, the Messaging Gateway processes it and then invokes the callback (see the following sequence diagram).
事件驱动(异步)消息传递网关
Event-Driven (Asynchronous) Messaging Gateway
例如,在 C# 中,使用委托,消息传递网关可以公开以下公共接口:
For example, in C#, using delegates, the Messaging Gateway could expose the following public interface:
delegate void OnCreditReplyEvent(int CreditScore); void RequestCreditScore(string SSN, OnCreditReplyEvent OnCreditResponse);
delegate void OnCreditReplyEvent(int CreditScore); void RequestCreditScore(string SSN, OnCreditReplyEvent OnCreditResponse);
RequestCreditScore方法接受一个附加参数,该参数指定回复消息到达时要调用的回调方法。回调方法有一个参数CreditScore,以便消息网关可以将结果传递给应用程序。根据编程语言或平台,回调可以通过函数指针、对象引用或委托来完成(如此处所示)。请注意,尽管此接口具有事件驱动的性质,但根本不依赖于特定的消息传递技术。
The method RequestCreditScore accepts an additional parameter that specifies the callback method to be invoked when the reply message arrives. The callback method has a parameter CreditScore so that the Messaging Gateway can pass the results to the application. Depending on the programming language or platform, the callback can be accomplished with function pointers, object references, or delegates (as shown here). Note that despite the event-driven nature of this interface, there is no dependency at all on a specific messaging technology.
或者,应用程序可以定期轮询以查看结果是否到达。这种方法使高层接口变得简单,且不会引入阻塞,本质上是采用半同步/半异步模式 [ POSA2 ]。此模式描述了使用存储传入消息的缓冲区,以便应用程序可以在方便时轮询以查看消息是否已到达。
Alternatively, the application can periodically poll to see whether the results arrived. This approach makes the higher-level interface simple without introducing blocking, essentially employing the Half-Sync/Half-Async pattern [POSA2]. This pattern describes the use of buffers that store incoming messages so that the application can poll at its convenience to see whether a message has arrived.
使用事件驱动的消息传递网关的挑战之一是消息传递网关要求应用程序维护请求方法和回调事件之间的状态(调用堆栈在阻塞情况下负责处理此问题)。当消息传递网关将回调事件调用到应用程序逻辑中时,应用程序必须能够将回复与之前发出的请求关联起来,以便它可以继续处理正确的执行线程。如果消息传递网关允许应用程序将对任意数据集的引用传递给请求方法,那么它可以使应用程序更轻松地维护状态。消息传送网关然后将通过回调将此数据传递回应用程序。这样,当调用异步回调时,应用程序就可以获得所有必需的数据。这种类型的交互通常称为 ACT(异步完成令牌)[ POSA2 ]。
One of the challenges of using an event-driven Messaging Gateway is that the Messaging Gateway requires the application to maintain state between the request method and the callback event (the call stack takes care of this in the blocking case). When the Messaging Gateway invokes the callback event into the application logic, the application must be able to correlate the reply with the request it made earlier so that it can continue processing the correct thread of execution. The Messaging Gateway can make it easier for the application to maintain state if it allows the application to pass a reference to an arbitrary set of data to the request method. The Messaging Gateway will then pass this data back to the application with the callback. This way, the application has all necessary data available when the asynchronous callback is invoked. This type of interaction is commonly called ACT (Asynchronous Completion Token) [POSA2].
支持 ACT 的事件驱动消息传递网关的公共接口可能如下所示:
The public interface of an event-driven Messaging Gateway that supports an ACT may look like this:
delegate void OnCreditReplyEvent(int CreditScore, Object ACT); void RequestCreditScore(string SSN, OnCreditReplyEvent OnCreditResponse, 对象 ACT);
delegate void OnCreditReplyEvent(int CreditScore, Object ACT); void RequestCreditScore(string SSN, OnCreditReplyEvent OnCreditResponse, Object ACT);
RequestCreditScore方法有一个附加参数,即对通用对象的引用。消息网关存储该引用,等待回复消息到达。当回复到达时,网关调用OnCreditReplyEvent类型的委托,传入操作结果以及对象引用。虽然支持 ACT 对于应用程序来说是一个非常方便的功能,但如果消息传递网关维护对对象的引用但预期的回复消息永远不会到达,它确实会带来内存泄漏的危险。
The method RequestCreditScore features an additional parameter, a reference to a generic object. The Messaging Gateway stores this reference, waiting for the reply message to arrive. When the reply arrives, the gateway invokes the delegate of type OnCreditReplyEvent, passing in the result of the operation as well as the object reference. While supporting an ACT is a very convenient feature for the application, it does introduce the danger of a memory leak if the Messaging Gateway maintains a reference to an object but the expected reply message never arrives.
创建多于一层的消息传递网关可能是有益的。“较低级别”消息传递网关可以简单地抽象消息传递系统的语法,但维护通用消息传递语义,例如SendMessage 。当企业更改消息传递技术(例如从 MSMQ 到 Web 服务)时,此消息传递网关可以帮助屏蔽应用程序的其余部分。我们用一个附加的消息传递网关包装这个基本的消息传递网关,该网关将通用消息传递 API 转换为狭窄的、特定于域的 API,例如GetCreditScore。我们在 Loan Broker 示例的 MSMQ 实现中使用此配置(参见下图;另请参见第 9 章“插曲:组合消息传递”中的“ MSMQ 异步实现”部分)。
It can be beneficial to create more than one layer of Messaging Gateways. The "lower-level" Messaging Gateway can simply abstract the syntax of the messaging system but maintain generic messaging semantics, for example, SendMessage. This Messaging Gateway can help shield the rest of the application when the enterprise changes messaging technologies, for example, from MSMQ to Web services. We wrap this basic Messaging Gateway with an additional Messaging Gateway that translates the generic messaging API into a narrow, domain-specific API, such as GetCreditScore. We use this configuration in the MSMQ implementation of the Loan Broker example (see the following figure; also see the section "Asynchronous Implementation with MSMQ" in Chapter 9, "Interlude: Composed Messaging").
网关链提供不同的抽象级别
A Chain of Gateways Provides Different Levels of Abstraction
除了简化应用程序编码之外,消息传递网关的目的还在于消除应用程序代码对特定消息传递技术的依赖。通过将任何特定于消息传递的方法调用包装在消息传递网关接口后面,可以轻松实现这一点。然而,大多数消息传递层都会抛出特定于消息传递的异常,例如JMS 引发的 InvalidDestinationException 。如果我们真的想让我们的应用程序代码独立于消息传递库,那么消息传递网关必须捕获任何特定于消息传递的异常并抛出特定于应用程序的(或通用)异常。这段代码可能会有点乏味,但如果我们必须切换底层实现(例如从 JMS 到 Web 服务),它会非常有帮助。
Besides making coding the application simpler, the intent of the Messaging Gateway is also to eliminate dependencies of the application code on specific messaging technologies. This is easy to do by wrapping any messaging-specific method calls behind the Messaging Gateway interface. However, most messaging layers throw messaging-specific exceptions, such as the InvalidDestinationException raised by JMS. If we really want to make our application code independent from the messaging library, the Messaging Gateway has to catch any messaging-specific exception and throw an application-specific (or a generic) exception instead. This code can get a little tedious, but it is very helpful if we ever have to switch the underlying implementations, for instance, from JMS to Web services.
在许多情况下,我们可以根据外部资源公开的元数据生成消息传递网关代码。这在 Web 服务领域很常见。几乎每个供应商或开源平台都提供了wsdl2java等工具连接到外部 Web 服务公开的 Web 服务描述语言 (WSDL)。该工具生成 Java(或 C#,或您需要的任何语言)类,封装所有讨厌的 SOAP 内容并公开简单的函数调用。我们创建了一个类似的工具,可以从 TIBCO 存储库读取消息模式定义,并为模拟模式定义的类创建 Java 源代码。这允许应用程序开发人员发送正确类型的 TIBCO ActiveEnterprise 消息,而无需学习 TIBCO API。
In many situations, we can generate the Messaging Gateway code from metadata exposed by the external resource. This is common in the world of Web services. Almost every vendor or open source platform provides a tool such as wsdl2java that connects to the Web Service Description Language (WSDL) exposed by an external Web service. The tool generates Java (or C#, or whatever language you need) classes that encapsulate all the nasty SOAP stuff and expose a simple function call. We created a similar tool that can read message schema definitions off the TIBCO repository and creates Java source code for a class that mimics the schema definition. This allows application developers to send correctly typed TIBCO ActiveEnterprise messages without having to learn the TIBCO API.
消息传递网关是出色的测试工具。因为我们将所有消息传递代码包装在一个狭窄的、特定于域的接口后面,所以我们可以轻松创建该接口的虚拟实现。我们只是将接口和实现分开,并提供两种实现:一种是访问消息传递基础设施的“真实”实现,另一种是用于测试目的的“假”实现(见图)。伪造的实现充当服务存根[ EAA ],并允许我们在不依赖消息传递的情况下测试应用程序。服务存根还可用于调试使用事件驱动消息传递网关的应用程序。例如,事件驱动消息传递网关的简单测试存根可以直接从请求方法调用回调(或委托),从而在一个线程中有效地执行请求和响应处理。这可以极大地简化逐步调试。
Messaging Gateways make great testing vehicles. Because we wrapped all the messaging code behind a narrow, domain-specific interface, we can easily create a dummy implementation of this interface. We simply separate interface and implementation and provide two implementations: one "real" implementation that accesses the messaging infrastructure and a "fake" implementation for testing purposes (see figure). The fake implementation acts as a Service Stub [EAA] and allows us to test the application without any dependency on messaging. A Service Stub can also be useful to debug an application that uses an event-driven Messaging Gateway. For example, a simple test stub for an event-driven Messaging Gateway can simply invoke the callback (or delegate) right from the request method, effectively executing both the request and the response processing in one thread. This can simplify step-by-step debugging enormously.
网关作为测试工具
Gateways as a Testing Tool
|
示例: MSMQ 中的异步贷款经纪人网关 Example: Asynchronous Loan Broker Gateway in MSMQ 此示例显示了第 9 章“插曲:组合消息传递”中介绍的贷款经纪人示例的一部分(请参阅“使用 MSMQ 进行异步实现”)。 This example shows a piece of the Loan Broker example introduced in Chapter 9, "Interlude: Composed Messaging" (see "Asynchronous Implementation with MSMQ"). 公共委托 void OnCreditReplyEvent(CreditBureauReply public delegate void OnCreditReplyEvent(CreditBureauReply creditReply, Object ACT); internal struct CreditRequestProcess { public int CorrelationID; public Object ACT; public OnCreditReplyEvent callback; } internal class CreditBureauGateway { protected IMessageSender creditRequestQueue; protected IMessageReceiver creditReplyQueue; protected IDictionary activeProcesses = (IDictionary)(new Hashtable()); protected Random random = new Random(); public void Listen() { creditReplyQueue.Begin(); } public void GetCreditScore(CreditBureauRequest quoteRequest, OnCreditReplyEvent OnCreditResponse, Object ACT) { Message requestMessage = new Message(quoteRequest); requestMessage.ResponseQueue = creditReplyQueue.GetQueue(); requestMessage.AppSpecific = random.Next(); CreditRequestProcess processInstance = new CreditRequestProcess(); processInstance.ACT = ACT; processInstance.callback = OnCreditResponse; processInstance.CorrelationID = requestMessage.AppSpecific; creditRequestQueue.Send(requestMessage); activeProcesses.Add(processInstance.CorrelationID, processInstance); } private void OnCreditResponse(Message msg) { msg.Formatter = GetFormatter(); CreditBureauReply replyStruct; try { if (msg.Body is CreditBureauReply) { replyStruct = (CreditBureauReply)msg.Body; int CorrelationID = msg.AppSpecific; if (activeProcesses.Contains(CorrelationID)) { CreditRequestProcess processInstance = (CreditRequestProcess) (activeProcesses[CorrelationID]); processInstance.callback(replyStruct, processInstance.ACT); activeProcesses.Remove(CorrelationID); } else { Console.WriteLine("Incoming credit response does not match any request"); } } else { Console.WriteLine("Illegal reply."); } } catch (Exception e) { Console.WriteLine("Exception: {0}", e.ToString()); } } } 您会注意到公共方法GetCreditScore和公共委托OnCreditReplyEvent根本不引用消息传递。此实现允许调用应用程序将任意对象引用作为 ACT 传递。CreditBureauGateway将此对象引用存储在由请求消息相关标识符索引的字典中。当回复消息到达时,CreditBureauGateway 可以检索与出站请求消息关联的数据。调用应用程序不必担心消息如何关联。 You will notice that the public method GetCreditScore and the public delegate OnCreditReplyEvent make no references to messaging at all. This implementation allows the calling application to pass an arbitrary object reference as an ACT. The CreditBureauGateway stores this object reference in a dictionary indexed by the Correlation Identifier of the request message. When the reply message arrives, the CreditBureauGateway can retrieve the data that was associated with the outbound request message. The calling application does not have to worry about how the messages are correlated. |
当应用程序使用消息传递时,消息的数据通常源自应用程序的域对象。如果我们使用文档消息,消息本身可能直接代表一个或多个域对象。如果我们使用命令消息,与命令关联的一些数据字段也可能从域对象中提取。消息和对象之间存在一些明显的差异。例如,大多数对象依赖于对象引用和继承关系形式的关联。许多消息传递基础设施不支持这些概念,因为它们必须能够与一系列应用程序通信,其中一些应用程序可能根本不是面向对象的。
When applications use Messaging, the Messages' data is often derived from the applications' domain objects. If we use a Document Message, the message itself may directly represent one or more domain objects. If we use a Command Message, some of the data fields associated with the command are likely to be extracted from domain objects as well. There are some distinct differences between messages and objects. For example, most objects rely on associations in the form of object references and inheritance relationships. Many messaging infrastructures do not support these concepts because they have to be able to communicate with a range of applications, some of which may not be object-oriented at all.
|
如何在域对象和消息传递基础设施之间移动数据,同时保持两者相互独立? How do you move data between domain objects and the messaging infrastructure while keeping the two independent of each other? |
为什么我们不能让我们的消息看起来与域对象完全相同并让问题消失?在许多情况下,我们无法控制消息格式,因为它是由规范数据模型或通用消息传递标准(例如 ebXML)定义的。我们仍然可以以与域对象相对应的格式发布消息,并使用消息传递层内的消息转换器对通用消息格式进行必要的转换。这种方法通常由不允许在应用程序内部进行转换的第三方系统适配器(例如数据库适配器)使用。
Why can't we make our messages look exactly like the domain objects and make the problem go away? In many cases, we are not in control of the message format because it is defined by a Canonical Data Model or a common messaging standard (e.g., ebXML). We could still publish the message in a format that corresponds to the domain object and use a Message Translator inside the messaging layer to make the necessary transformation to the common message format. This approach is commonly used by adapters to third-party systems that do not allow transformation inside the application (e.g., a database adapter).
或者,域层可以以所需的格式创建和发布消息,而不需要单独的消息转换器。此选项很可能会带来更好的性能,因为我们不发布中间消息。此外,如果我们的领域模型包含许多小对象,那么首先将它们组合成单个消息可能会有益于简化路由并提高消息传递层内的效率。即使我们能够承担额外的转换步骤,如果我们想要创建模仿域对象的消息,我们也会遇到限制。这种方法的缺点是域变得依赖于消息格式,如果消息格式发生变化,这使得域维护变得困难。
Alternatively, the domain layer can create and publish a message in the required format without the need for a separate Message Translator. This option most likely results in better performance because we do not publish an intermediate message. Also, if our domain model contains many small objects, it may be beneficial to combine them into a single message first to simplify routing and improve efficiency inside the messaging layer. Even if we can afford the additional transformation step, we will run into limitations if we want to create messages that mimic domain objects. The shortcoming of this approach is that the domain becomes dependent on the message format, which makes domain maintenance difficult if the message format changes.
大多数消息传递基础设施都支持“消息”对象的概念作为 API 的一部分。该消息对象封装了要通过通道发送的数据。在大多数情况下,此消息对象只能包含标量数据类型,例如字符串、数字或日期,但不支持继承或对象引用。这是 RPC 式通信(即 RMI)和异步消息传递系统之间的主要区别之一。假设我们发送一条异步消息,其中包含对组件的对象引用。为了处理消息,组件必须解析对象引用。它将通过从消息源请求对象来完成此操作。然而,请求-回复交互首先会破坏使用异步消息传递的一些动机(即组件之间的松散耦合)。更糟糕的是,当订阅者收到异步消息时,所引用的对象可能不再存在于源系统中。
Most messaging infrastructures support the notion of a "Message" object as part of the API. This message object encapsulates the data to be sent over a channel. In most cases, this message object can contain only scalar datatypes such as strings, numbers, or dates, but does not support inheritance or object references. This is one of the key differences between RPC-style communications (i.e., RMI) and asynchronous messaging systems. Let's assume we send an asynchronous message containing an object reference to a component. In order to process the message, the component would have to resolve the object reference. It would do this by requesting the object from the message source. However, request-reply interaction would defeat some of the motivations of using asynchronous messaging in the first place (i.e., loose coupling between components). Worse yet, by the time the asynchronous message is received by the subscriber, the referenced object may no longer exist in the source system.
解决对象引用问题的一种尝试是遍历对象的依赖关系树并将所有依赖对象包含在消息中。例如,如果一个Order对象引用五个OrderItem对象,我们将在消息中包含这五个对象。这确保接收者可以访问“根”对象的所有数据引用。但是,如果我们使用具有许多相互关联的对象的细粒度域对象模型,消息的大小可能会迅速爆炸。人们希望对消息中包含的内容和不包含的内容有更多的控制。
One attempt to resolve the issue of object references is to traverse the dependency tree of an object and include all dependent objects in the message. For example, if an Order object references five OrderItem objects, we would include the five objects in the message. This ensures that the receiver has access to all data references by the "root" object. However, if we use a fine-grained domain object model with many interrelated objects, messages can quickly explode in size. It would be desirable to have more control over what is included in a message and what is not.
让我们假设我们的域对象是独立的,并且没有任何对其他对象的引用。我们仍然不能简单地将整个域对象放入消息中,因为大多数消息传递基础结构不支持对象,因为它们必须与语言无关(JMS 接口 ObjectMessage 和 .NET 的 System.Messaging 命名空间中的Message类是例外,因为这些消息传递系统是特定于语言的 [Java] 或特定于平台的 [.NET CLR])。我们可以考虑将对象序列化为字符串并将其存储在称为“数据”的字符串字段中,几乎每个消息系统都支持该字段。然而,这种方法也有缺点。首先,一个消息路由器将无法将对象属性用于路由目的,因为该字符串字段对于消息传递层来说是“不透明的”。它还会使测试和调试变得困难,因为我们必须破译数据字段的内容。此外,构建所有消息以使其仅包含单个字符串字段将不允许我们按消息类型路由消息,因为所有消息对于基础设施来说看起来都是相同的。验证消息的正确格式也很困难,因为消息传递基础设施不会验证数据字段内的任何内容。最后,我们将无法使用语言运行时库提供的序列化工具,因为这些表示通常不跨语言兼容。所以,
Let's assume for a moment that our domain object is self-contained and does not have any references to other objects. We still cannot simply stick the whole domain object into a message, as most messaging infrastructures do not support objects because they have to be language-independent (the JMS interface ObjectMessage and the Message class in .NET's System.Messaging namespace are exceptions, since these messaging systems are either language-specific [Java] or platform-specific [.NET CLR]). We could think of serializing the object into a string and storing it in a string field called "data," which is supported by pretty much every messaging system. However, this approach has disadvantages as well. First, a Message Router would not be able to use object properties for routing purposes because this string field would be "opaque" to the messaging layer. It would also make testing and debugging difficult, because we would have to decipher the contents of the data field. Also, constructing all messages so that they contain just a single string field would not allow us to route messages by message type because all messages look the same to the infrastructure. It would also be difficult to verify the correct format of the message because the messaging infrastructure would not verify anything inside the data field. Finally, we would not be able to use the serialization facilities provided by the language runtime libraries because these presentations are usually not compatible across languages. So, we would have to write our own serialization code.
一些消息传递基础设施现在支持消息内的 XML 字段,以便我们可以将对象序列化为 XML。这可以减轻一些缺点,因为现在消息更容易破译,并且某些消息传递层可以直接访问 XML 字符串内的元素。然而,我们现在必须处理相当详细的消息和有限的数据类型验证。另外,我们仍然需要创建将对象转换为 XML 并返回的代码。根据我们使用的编程语言,这可能非常复杂,特别是如果我们使用不支持反射的旧语言。
Some messaging infrastructures now support XML fields inside messages so that we could serialize objects into XML. This can alleviate some of the disadvantages because the messages are easier to decipher now and some messaging layers can access elements inside an XML string directly. However, we now have to deal with quite verbose messages and limited datatype validation. Plus, we still have to create code that translates an object into XML and back. Depending on the programming language we use, this could be quite complex, especially if we use an older language that does not support reflection.
出于多种原因,我们强烈建议将此映射代码与域对象分开。首先,我们可能不想将涉及低级语言功能的代码与应用程序逻辑混合在一起。在许多情况下,我们会有一组程序员专门负责消息传递层,而另一组则专注于领域逻辑。将两段代码粘贴到一个对象中将使团队难以并行工作。
We are well advised to separate this mapping code from the domain object for a number of reasons. First of all, we may not want to blend code that concerns itself with low-level language features with application logic. In many cases, we will have a group of programmers dedicated to working with the messaging layer, while another group focuses on the domain logic. Sticking both pieces of code into one object will make it difficult for the teams to work in parallel.
其次,将映射代码合并到域对象内使得域对象依赖于消息传送基础设施,因为映射代码需要调用消息传送API(例如,实例化消息对象) 。在大多数情况下,这种依赖性是不可取的,因为它会阻止在不使用消息传递或使用其他供应商的消息传递基础结构的另一个上下文中重用域对象。结果,我们将严重阻碍领域对象的可重用性。
Second, incorporating mapping code inside the domain object makes the domain object dependent on the messaging infrastructure because the mapping code will need to make calls into the messaging API (e.g., to instantiate the Message object). In most cases, this dependency is not desirable because it prevents the reuse of the domain objects in another context that does not use messaging or that uses another vendor's messaging infrastructure. As a result, we would seriously impede the reusability of the domain objects.
我们经常看到人们编写包装消息传递基础设施 API 的“抽象层”,从而有效地使处理消息传递的代码独立于消息传递 API。这样的层提供了一定程度的间接性,因为它将消息传递接口与消息传递实现分开。因此,即使我们必须切换到另一个供应商的消息传递层,我们也可以重用消息传递相关的代码。我们需要做的就是实现一个新的抽象层,将消息传递接口转换为新的 API。然而,这种方法并没有解决域对象对消息传递层的依赖性。域对象现在将包含对抽象消息传递接口的引用,而不是对特定于供应商的消息传递 API 的引用。
We often see people write "abstraction layers" that wrap the messaging infrastructure API, effectively making the code that deals with messaging independent from the messaging API. Such a layer provides a level of indirection because it separates the messaging interface from the messaging implementation. Therefore, we can reuse the messaging-related code even if we have to switch to another vendor's messaging layer. All we need to do is implement a new abstraction layer that translates the messaging interface to the new API. However, this approach does not resolve the dependency of the domain objects on the messaging layer. The domain objects would now contain references to the abstracted messaging interface as opposed to the vendor-specific messaging API. But we still cannot use the domain objects in a context that does not use messaging.
许多消息由多个域对象组成。由于我们无法通过消息传递基础结构传递对象引用,因此我们可能需要包含来自其他对象的字段。在某些情况下,我们可能会在一条消息中包含所有依赖对象的整个“依赖树”。哪个类应该保存映射代码?同一对象可能是与不同对象组合的多种消息类型的一部分,因此这个问题没有简单的答案。
Many messages are composed of more than one domain object. Since we cannot pass object references through the messaging infrastructure, it is likely that we need to include fields from other objects. In some cases, we may include the whole "dependency tree" of all dependent objects inside one message. Which class should hold the mapping code? The same object may be part of multiple message types combined with different objects, so there is no easy answer to this question.
|
创建一个单独的消息传递映射器,其中包含消息传递基础结构和域对象之间的映射逻辑。对象和基础设施都不知道消息传递映射器的存在。 Create a separate Messaging Mapper that contains the mapping logic between the messaging infrastructure and the domain objects. Neither the objects nor the infrastructure have knowledge of the Messaging Mapper's existence. |
消息传递映射器访问一个或多个域对象,并将它们转换为消息传递通道所需的消息。它还执行相反的功能,根据传入消息创建或更新域对象。由于消息传递映射器是作为引用域对象和消息传递层的单独类实现的,因此两层都不知道另一层。这些层甚至不知道消息映射器。
The Messaging Mapper accesses one or more domain objects and converts them into a message as required by the messaging channel. It also performs the opposite function, creating or updating domain objects based on incoming messages. Since the Messaging Mapper is implemented as a separate class that references both the domain object(s) and the messaging layer, neither layer is aware of the other. The layers don't even know about the Messaging Mapper.
消息传递映射器是映射器模式 [ EAA ]的特化。它与Data Mapper [ EAA ]有一些相似之处。任何研究过 OR(对象关系)映射策略的人都会理解在使用不同范例的层之间映射数据的复杂性。消息映射器中固有的问题同样复杂,对所有可能方面的详细讨论超出了本书的范围。[ EAA ]中的许多数据源架构模式对于任何关心创建消息映射器层的人来说都值得一读。
The Messaging Mapper is a specialization of the Mapper pattern [EAA]. It shares some analogies with Data Mapper [EAA]. Anyone who has worked on O-R (Object-Relational) mapping strategies will understand the complexities of mapping data between layers that use different paradigms. The issues inherent in the Messaging Mapper are similarly complex, and a detailed discussion of all possible aspects is beyond the scope of this book. Many of the Data Source Architectural Patterns in [EAA] make a good read for anyone concerned with creating a Messaging Mapper layer.
消息传递映射器与围绕消息传递 API 的抽象层的常用概念不同。在抽象层的情况下,域对象不知道消息传递 API,但它们确实知道抽象层(抽象层本质上执行消息传递网关的功能)。对于消息传递映射器来说,对象根本不知道我们正在处理消息传递。
A Messaging Mapper is different from the frequently used concept of an abstraction layer wrapped around the messaging API. In the case of an abstraction layer, the domain objects do not know about the messaging API, but they do know about the abstraction layer (the abstraction layer essentially performs the function of a Messaging Gateway ). In the case of a Messaging Mapper, the objects have no idea whatsoever that we are dealing with messaging.
消息映射器的意图与中介器 [ GoF ]类似,后者也用于分隔元素。但是,在使用 Mediator 的情况下,元素都知道Mediator,而两个元素都不知道Messaging Mapper 。
The intent of a Messaging Mapper is similar to that of a Mediator [GoF], which is also used to separate elements. In the case of a Mediator, though, the elements are aware of the Mediator, whereas neither element is aware of the Messaging Mapper.
如果域对象和消息传递基础设施都不知道消息传递映射器,那么如何调用它?在大多数情况下,消息传递映射器是通过消息传递基础结构或应用程序触发的事件来调用的。由于两者都不依赖于消息传递映射器,因此事件通知可以通过单独的代码片段或通过使消息传递映射器成为观察者模式[ GoF]来发生。例如,如果我们使用 JMS API 与消息传递基础设施交互,我们可以实现MessageListener接口来接收任何传入消息的通知。同样,我们可以使用观察者接收域对象内任何相关事件的通知并调用消息传递映射器。如果我们必须直接从应用程序调用消息传递映射器,我们应该定义一个消息传递映射器接口,以便应用程序至少不依赖于消息传递映射器实现。
If neither the domain objects nor the messaging infrastructure know about the Messaging Mapper, how does it get invoked? In most cases, the Messaging Mapper is invoked through events triggered by either the messaging infrastructure or the application. Since neither one is dependent on the Messaging Mapper, the event notification can happen either through a separate piece of code or by making the Messaging Mapper an Observer pattern [GoF]. For example, if we use the JMS API to interface with the messaging infrastructure, we can implement the MessageListener interface to be notified of any incoming messages. Likewise, we can use an Observer to be notified of any relevant events inside the domain objects and to invoke the Messaging Mapper. If we have to invoke the Messaging Mapper directly from the application, we should define a Messaging Mapper interface so that the application does at least not depend on the Messaging Mapper implementation.
一些消息映射器实现可能包含大量重复代码:从域对象获取字段并将其存储在消息对象中。继续到下一个字段并重复,直到完成所有字段。这可能非常乏味,而且还让人怀疑代码重复。我们有许多工具可以帮助我们避免这种单调乏味的情况。首先,我们可以编写一个通用的消息传递映射器它使用反射以通用方式从域对象中提取字段。例如,它可以遍历域对象内所有字段的列表,并将其存储在消息对象中的同名字段中。显然,只有当字段名称匹配时这才有效。根据我们之前的讨论,我们需要想出一些方法来解析对象引用,因为我们无法将它们存储在消息对象中。另一种方法是使用可配置的代码生成器来生成消息传递映射器代码。这使我们在字段命名方面具有更大的灵活性(消息字段名称和域对象字段名称不必匹配),并且我们可以设计巧妙的方法来处理对象引用。代码生成器的缺点是它们可能难以测试和调试,但如果我们使其足够通用,我们只需编写一次。
Some Messaging Mapper implementations may contain a lot of repetitive code: Get a field from the domain object and store it in a message object. Go on to the next field and repeat until all fields are done. This can be pretty tedious and also smells suspiciously like code duplication. We have a number of tools to help us avoid this tedium. First, we can write a generic Messaging Mapper that uses reflection to extract fields from a domain object in a generic way. For example, it could traverse the list of all fields inside the domain object and store it in a field of the same name in the message object. Obviously, this works only if the field names match. According to our previous discussions, we need to come up with some way to resolve object references, since we cannot store those in the message object. The alternative is to use a configurable code generator to generate the Messaging Mapper code. This allows us some more flexibility in the field naming (the message field name and the domain object field name do not have to match), and we can devise clever ways to deal with object references. The downside of code generators is that they can be difficult to test and debug, but if we make it generic enough, we have to write it only once.
某些框架(例如 Microsoft .NET)具有将对象内置到 XML 中的对象序列化功能,反之亦然,从而消除了对象序列化中涉及的大量繁重工作。即使框架完成了将对象转换为消息的一些工作,这种转换也仅限于翻译的语法级别。让框架完成所有工作可能很诱人,但它只会创建与域对象一一对应的消息。正如我们之前所解释的,这可能并不理想,因为消息的约束和设计标准与域对象的约束和设计标准有很大不同。定义一组与所需消息结构相对应的“接口对象”并让框架在消息和这些对象之间进行转换可能是有意义的。这然后,消息传递映射器层将管理真实域对象和接口对象之间的转换。这些接口对象与数据传输对象 [ EAA ]有一些相似之处,尽管动机略有不同。
Some frameworks, such as Microsoft .NET, feature built-in object serialization of objects into XML, and vice versa, and take away a lot of the grunt work involved in object serialization. Even if the framework does some of the legwork of converting an object into a message, this conversion is limited to the syntactic level of translation. It might be tempting to just let the framework do all the work, but it will just create messages that correspond to the domain objects one-to-one. As we explained earlier, this may not be desirable because the constraints and design criteria for messages are quite different from those for domain objects. It may make sense to define a set of "interface objects" that correspond to the desired messages structure and let the framework do the conversion between the messages and these objects. The Messaging Mapper layer will then manage the translation between the true domain objects and the interface objects. These interface objects bear some resemblance to Data Transfer Objects [EAA] even though the motivations are slightly different.
即使我们使用消息传递映射器,使用消息转换器将消息传递为符合规范仍然有意义。这为我们提供了额外的间接级别。我们可以使用消息映射器来解决诸如对象引用和数据类型转换之类的问题,并将结构映射留给消息转换器消息层内部。我们为这种额外的解耦付出的代价是创建一个额外的组件和一个小的性能损失。此外,有时在应用程序的编程语言内执行复杂的转换比使用集成供应商提供的拖放“涂鸦软件”更容易。
Even if we use a Messaging Mapper, it still makes sense to use a Message Translator to translate the messages generated by the Messaging Mapper into messages compliant with the Canonical Data Model. This gives us an additional level of indirection. We can use the Messaging Mapper to resolve issues such as object references and datatype conversions, and leave structural mappings to a Message Translator inside the messaging layer. The price we pay for this additional decoupling is the creation of an additional component and a small performance penalty. Also, sometimes it is easier to perform complex transformations inside the application's programming language than to use the drag-and-drop "doodleware" supplied by the integration vendor.
如果我们同时使用消息映射器和消息转换器,我们将在规范数据格式和域对象之间获得额外的间接级别。有人说,在计算机科学领域,每个问题都可以通过增加一层间接来解决,那么这个规则在这里是否成立呢?额外的间接使我们能够补偿消息传递层内规范模型的变化,而无需触及应用程序代码。它还允许我们通过保留繁琐的字段映射和数据类型更改(例如,将数字ZIP_Code字段转换为字母数字Postal_Code)来简化应用程序内部的映射逻辑字段)到针对此类工作优化的消息传递层的映射工具。然后,消息传递映射器将主要处理对象引用的解析和消除不必要的域对象细节。额外间接级别的明显缺点是域对象的更改现在可能需要更改消息映射器和消息转换器。如果我们确实能够生成消息传递映射器代码,那么这个问题就基本上消失了。
If we use both a Messaging Mapper and a Message Translator, we gain an additional level of indirection between the canonical data format and the domain objects. It has been said that computer science is the area where every problem can be solved by adding just one more level of indirection, so does this rule hold true here? The additional indirection gives us the ability to compensate for changes in the canonical model inside the messaging layer without having to touch application code. It also allows us to simplify the mapping logic inside the application by leaving tedious field mappings and datatype changes (e.g., a numeric ZIP_Code field to an alphanumeric Postal_Code field) to the mapping tools of messaging layer that are optimized for this kind of work. The Messaging Mapper would then primarily deal with the resolution of object references and the elimination of unnecessary domain object detail. The apparent downside of the extra level of indirection is that a change in the domain object may now require changes to both the Messaging Mapper and the Message Translator. If we did manage to generate the Messaging Mapper code, this issue largely goes away.
结合映射器和消息转换器
Combining Mapper and Message Translator
|
示例: JMS 中的消息传递映射器 Example: Messaging Mapper in JMS Aggregator JMS 示例中的AuctionAggregate类充当JMS 消息传递系统和 Bid 类之间的消息传递映射器。 addMessage和getResultMessage方法在 JMS 消息和 Bid对象进行转换。消息传递系统和Bid类都不知道这种交互。 The AuctionAggregate class in the Aggregator JMS example acts as a Messaging Mapper between the JMS messaging system and the Bid class. The methods addMessage and getResultMessage convert between JMS messages and Bid objects. Neither the messaging system nor the Bid class has any knowledge of this interaction. |
消息传递系统必然在内部使用事务行为。对于外部客户端来说,能够控制影响其行为的事务范围可能很有用。
A messaging system, by necessity, uses transactional behavior internally. It may be useful for an external client to be able to control the scope of the transactions that impact its behavior.
|
客户端如何通过消息系统控制其交易? How can a client control its transactions with the messaging system? |
消息传递系统必须在内部使用事务。单个消息通道可以有多个发送者和多个接收者,因此消息系统必须协调消息以确保发送者不会覆盖彼此的消息,多个点对点通道接收者不会收到相同的消息,多个发布-订阅频道每个接收者都会收到每条消息的一份副本,依此类推。为了管理所有这些,消息传递系统在内部使用事务来确保消息被添加或不添加到通道中,以及从通道中读取或不读取消息。消息传递系统还必须采用事务(最好是两阶段分布式事务)将消息从发送者的计算机复制到接收者的计算机,以便在任何给定时间,该消息“真正”仅在一台计算机或另一台计算机上。
A messaging system must use transactions internally. A single Message Channel can have multiple senders and multiple receivers, so the messaging system must coordinate the messages to make sure senders don't overwrite each other's Messages, multiple Point-to-Point Channel receivers don't receive the same message, multiple Publish-Subscribe Channel receivers each receive one copy of each message, and so on. To manage all of this, messaging systems internally use transactions to make sure a message gets added or doesn't get added to the channel, and gets read or doesn't get read from the channel. Messaging systems also have to employ transactionspreferably two-phase, distributed transactionsto copy a message from the sender's computer to the receiver's computer, such that at any given time, the message is "really" only on one computer or the other.
发送和接收消息的消息端点是事务性的,即使它们没有意识到这一点。将消息添加到通道的发送方法在事务中执行此操作,以将该消息与同时添加到该通道或从该通道删除的任何其他消息隔离。同样,接收方法也使用事务,这可以防止其他点对点接收方获取相同的消息,甚至确保发布-订阅接收方不会两次读取相同的消息。
Message Endpoints sending and receiving messages are transactional, even if they don't realize it. The send method that adds a message to a channel does so within a transaction to isolate that message from any other messages simultaneously being added to or removed from that channel. Likewise, a receive method also uses a transaction, which prevents other point-to-point receivers from getting the same message and even assures that a publish-subscribe receiver won't read the same message twice.
事务通常被描述为 ACID:原子、一致、隔离和持久。只有保证消息传递的事务才是持久的,并且根据定义,消息是原子的。但所有消息传递事务都必须一致且隔离。消息不能存在于通道中——无论是存在还是不存在。此外,应用程序的消息发送和接收必须与可能通过同一通道发送和接收消息的任何其他线程和应用程序隔离。
Transactions are often described as being ACID: atomic, consistent, isolated, and durable. Only transactions for Guaranteed Messaging are durable, and a message by definition is atomic. But all messaging transactions have to be consistent and isolated. A message can't be sort of in the channelit either is or isn't. Also, an application's sending and receiving of messages has to be isolated from whatever other threads and applications might be sending and receiving messages via the same channel.
对于只想发送或接收单个消息的客户端来说,消息传递系统的内部事务足够且方便。然而,应用程序可能需要更广泛的事务来协调多个消息或协调与其他资源的消息传递。像这样的常见场景包括
The messaging system's internal transactions are sufficient and convenient for a client that simply wants to send or receive a single message. However, an application may need a broader transaction to coordinate several messages or to coordinate messaging with other resources. Common scenarios like this include
Send-Receive Message Pairs Receive one message and send another, such as a Request-Reply scenario or when implementing a message filter such as a Message Router or Message Translator.
消息组 发送或接收一组相关消息,例如消息序列。
Message Groups Send or receive a group of related messages, such as a Message Sequence.
消息/数据库协调 将发送或接收消息与更新数据库相 结合,例如使用通道适配器。例如,当应用程序接收并处理订购产品的消息时,应用程序还需要更新产品库存数据库。同样,文档消息的发送者可能希望删除持久化的文档,但前提是该文档发送成功;接收方可能希望在消息真正被视为已被使用之前保留该文档。
Message/Database Coordination Combine sending or receiving a message with updating a database, such as with a Channel Adapter. For example, when an application receives and processes a message for ordering a product, the application will also need to update the product inventory database. Likewise, the sender of a Document Message may wish to delete a persisted document, but only when it is sent successfully; the receiver may want to persist the document before the message is truly considered to be consumed.
消息/工作流协调 使用一 对请求-答复消息来执行工作项,并使用事务来确保除非同时发送请求,否则不会获取工作项,并且除非同时发送请求,否则工作项不会完成或中止也收到回复。
Message/Workflow Coordination Use a pair of Request-Reply messages to perform a work item, and use transactions to ensure that the work item isn't acquired unless the request is also sent, and the work item isn't completed or aborted unless the reply is also received.
像这样的场景需要更大的原子事务,该事务不仅仅涉及单个消息,还可能涉及除消息传递系统之外的其他事务存储。需要事务,以便如果场景的一部分起作用(例如接收消息)但另一部分不起作用(例如更新数据库或发送另一条消息),则所有部分都可以回滚,就好像它们从未发生过一样,并且然后应用程序可以重试。
Scenarios like these require a larger atomic transaction that involve more than just a single message and may involve other transactional stores besides the messaging system. A transaction is required so that if part of the scenario works (receiving the message, for example) but another part does not (such as updating the database or sending another message), all parts can be rolled back as if they never happened, and then the application can try again.
然而,消息传递系统的内部事务模型不足以允许应用程序协调处理消息与其他消息或其他资源。应用程序需要一种从外部控制消息传递系统的事务并将它们与消息传递系统或其他地方的其他事务组合的方法。
Yet a messaging system's internal transaction model is insufficient to allow an application to coordinate handling a message with other messages or other resources. What is needed is a way for the application to externally control the messaging system's transactions and combine them with other transactions in the messaging system or elsewhere.
|
使用 事务性客户端使 客户端与消息传递系统的会话成为事务性的,以便客户端可以指定事务边界。 Use a Transactional Client make the client's session with the messaging system transactional so that the client can specify transaction boundaries. |
发送者和接收者都可以是事务性的。对于发送者,在发送者提交事务之前,消息实际上不会添加到通道中。对于接收者,在接收者提交事务之前,消息实际上不会从通道中删除。使用显式事务的发送方可以与使用隐式事务的接收方一起使用,反之亦然。单个通道可能具有隐式和显式事务发送者的组合;它还可以具有接收器的组合。
Both a sender and a receiver can be transactional. With a sender, the message isn't actually added to the channel until the sender commits the transaction. With a receiver, the message isn't actually removed from the channel until the receiver commits the transaction. A sender that uses explicit transactions can be used with a receiver that uses implicit transactions, and vice versa. A single channel might have a combination of implicitly and explicitly transactional senders; it could also have a combination of receivers.
事务接收器序列
Transactional Receiver Sequence
使用事务接收器,应用程序可以接收消息,而无需实际从队列中删除消息。此时,如果应用程序崩溃,当它恢复时,消息仍然会在队列中;消息不会丢失。收到消息后,应用程序可以对其进行处理。一旦应用程序完成消息并且确定想要使用它,应用程序就会提交事务,该事务(如果成功)会从通道中删除消息。此时,如果应用程序崩溃,当它恢复时,消息将不再在通道上,因此应用程序最好真正完成消息处理。
With a transactional receiver, an application can receive a message without actually removing the message from the queue. At this point, if the application crashed, when it recovered, the message would still be on the queue; the message would not be lost. Having received the message, the application can then process it. Once the application is finished with the message and is certain it wants to consume it, the application commits the transaction, which (if successful) removes the message from the channel. At this point, if the application crashed, when it recovered, the message would no longer be on the channel, so the application had better truly be finished with the message.
从外部控制消息系统的事务如何帮助应用程序协调多个任务?以下是应用程序在前面描述的场景中将执行的操作:
How does controlling a messaging system's transactions externally help an application coordinate several tasks? Here's what the application would do in the scenarios described earlier:
发送-接收消息对
Send-Receive Message Pairs
要做什么: 启动事务,接收并处理第一条消息,创建并发送第二条消息,然后提交。(此行为通常作为Request-Reply、Message Router和Message Translator的一部分实现。 )
What to do: Start a transaction, receive and process the first message, create and send the second message, then commit. (This behavior is often implemented as part of Request-Reply, Message Router, and Message Translator.)
作用: 这可以防止第一条消息从其通道中删除,直到第二条消息成功添加到其通道中。
What this does: This keeps the first message from being removed from its channel until the second message is successfully added to its channel.
交易类型: 如果两条消息通过同一消息系统中的通道发送,则包含这两个通道的交易是简单交易。但是,如果这两个通道由两个独立的消息传递系统(例如消息桥)管理,则事务将是协调两个消息传递系统的分布式事务。
Transaction type: If the two messages are sent via channels in the same messaging system, the transaction encompassing the two channels is a simple one. However, if the two channels are managed by two separate messaging systems, such as with a Messaging Bridge, the transaction will be a distributed one coordinating the two messaging systems.
警告: 单个事务仅适用于发送回复的请求的接收者。请求的发送者不能使用单个事务来发送请求并等待其回复。如果尝试这样做,请求将永远不会真正发送,因为发送事务未提交,因此永远不会收到回复。
Warning: A single transaction only works for the receiver of a request sending a reply. The sender of a request cannot use a single transaction to send a request and wait for its reply. If it tries to do this, the request will never really be sentbecause the send transaction isn't committedso the reply will never be received.
消息组
Message Groups
要做什么: 启动事务,发送或接收组中的所有消息(例如消息序列) ,然后提交。
What to do: Start a transaction, send or receive all of the messages in the group (such as a Message Sequence ), then commit.
作用: 发送时,组中的任何消息都不会添加到通道中,直到全部发送成功。接收时,在所有消息都收到之前,不会从通道中删除任何消息。
What this does: When sending, none of the messages in the group will be added to the channel until they are all successfully sent. When receiving, none of the messages will be removed from the channel until all are received.
事务类型: 由于所有消息都发送到单个通道或从单个通道接收,因此该通道将由单个消息系统管理,因此事务将是一个简单的事务。此外,在许多消息传递系统实现中,在单个事务中发送一组消息可确保它们将按照发送顺序在通道的另一端接收。
Transaction type: Since all of the messages are being sent to or received from a single channel, that channel will be managed by a single messaging system, so the transaction will be a simple one. Also, in many messaging system implementations, sending a group of messages in a single transaction ensures that they will be received on the other end of the channel in the order they were sent.
消息/数据库协调
Message/Database Coordination
要做什么: 启动事务,接收消息,更新数据库,然后提交。或者,更新数据库并发送消息向其他人报告更新,然后提交。(此行为通常由 Channel Adapter 实现。)
What to do: Start a transaction, receive a message, update the database, and then commit. Or, update the database and send a message to report the update to others, and then commit. (This behavior is often implemented by a Channel Adapter.)
作用: 除非更新数据库,否则消息不会被删除(或者如果无法发送消息,则数据库更改将不会保留)。
What this does: The message will not be removed unless the database is updated (or the database change will not stick if the message cannot be sent).
事务类型: 由于消息系统和数据库都有自己的事务管理器,因此协调它们的事务将是分布式事务。
Transaction type: Since the messaging system and the database each has its own transaction manager, the transaction to coordinate them will be a distributed one.
消息/工作流程协调
Message/Workflow Coordination
要做什么: 使用一对请求-答复消息来执行工作项。启动事务,获取工作项,发送请求消息,然后提交。或者,启动另一个事务,接收回复消息,完成或中止工作项,然后提交。
What to do: Use a pair of Request-Reply messages to perform a work item. Start a transaction, acquire the work item, send the request message, and then commit. Or, start another transaction, receive the reply message, complete or abort the work item, then commit.
作用: 除非发送请求,否则工作项不会被提交;除非工作项更新,否则回复不会被删除。
What this does: The work item will not be committed unless the request is sent; the reply will not be removed unless the work item is updated.
事务类型: 由于消息系统和工作流引擎都有自己的事务管理器,因此协调它们的事务将是分布式事务。
Transaction type: Since the messaging system and the workflow engine each has its own transaction manager, the transaction to coordinate them will be a distributed one.
通过这种方式,应用程序可以确保它不会丢失收到的消息或忘记发送它应该发送的消息。如果中间出现问题,应用程序可以回滚事务并重试。
In this way, the application can assure that it will not lose the messages it receives or forget to send a message that it should. If something goes wrong in the middle, the application can roll back the transaction and try again.
使用事件驱动消费者的事务客户端可能无法按预期工作。消费者通常必须在将消息传递给应用程序之前提交用于接收消息的事务。然后,如果应用程序检查消息并决定不想使用它,或者如果应用程序遇到错误并想要回滚消费操作,则它不能,因为它无权访问事务。因此,无论客户端是否是事务性的,事件驱动的消费者都倾向于以相同的方式工作。
Transactional clients using Event-Driven Consumers may not work as expected. The consumer typically must commit the transaction for receiving the message before passing the message to the application. Then, if the application examines the message and decides it does not want to consume it, or if the application encounters an error and wants to roll back the consume action, it cannot because it does not have access to the transaction. So, an event-driven consumer tends to work the same whether or not its client is transactional.
消息系统能够参与分布式事务,尽管某些实现可能不支持它。在 JMS 中,提供者可以充当 XA 资源并参与 Java Transaction API [ JTA ] 事务。此行为由javax.jms包(特别是javax.jms.XASession )中的 XA 类以及javax.transaction.xa包定义。JMS 规范建议 JMS 客户端不要尝试直接处理分布式事务,因此应用程序应该使用 J2EE 应用程序服务器提供的分布式事务支持。MSMQ还可以参与XA事务;此行为在 .NET 中由MessageQueue.Transactional属性和MessageQueueTransaction 类。
Messaging systems are capable of participating in a distributed transaction, although some implementations may not support it. In JMS, a provider can act as an XA resource and participate in Java Transaction API [JTA] transactions. This behavior is defined by the XA classes in the javax.jms package, particularly javax.jms.XASession, and by the javax.transaction.xa package. The JMS specification recommends that JMS clients not try to handle distributed transactions directly, so an application should use the distributed transaction support provided by a J2EE application server. MSMQ can also participate in an XA transaction; this behavior is exposed in .NET by the MessageQueue.Transactional property and the MessageQueueTransaction class.
如前所述,事务客户端可用作其他模式的一部分,例如请求-答复、管道和过滤器中的消息过滤器、消息序列和通道适配器。同样,事件消息的接收者可能希望在从通道中完全删除其消息之前完成事件的处理。然而,事务性客户端不能与事件驱动的消费者或消息调度器一起很好地工作,可能会给竞争,但与单个轮询消费者一起工作却很好。
As discussed earlier, Transactional Clients can be useful as part of other patterns, such as Request-Reply, message filters in Pipes and Filters, Message Sequence, and Channel Adapter. Likewise, the receiver of an Event Message may want to complete processing the event before removing its message from the channel completely. However, Transactional Clients do not work well with Event-Driven Consumers or Message Dispatchers, can cause problems for Competing Consumers, but work well with a single Polling Consumer.
|
示例: JMS 事务处理会话 Example: JMS Transacted Session 在 JMS 中,客户端在创建其会话时使其自身具有事务性 [ JMS 1.1 ]、[ Hapner ]。 In JMS, a client makes itself transactional when it creates its session [JMS 1.1], [Hapner]. Connection 连接 = // 获取连接
会话会话=
连接.createSession(true, Session.AUTO_ACKNOWLEDGE);
Connection connection = // Get the connection
Session session =
connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
此会话是事务性的,因为第一个createSession参数设置为 true 。 This session is transactional because the first createSession parameter is set to true. 当客户端使用事务会话时,它必须显式提交发送和接收以使它们真实。 When a client is using a transactional session, it must explicitly commit sends and receives to make them real. Queue队列= //获取队列 MessageConsumer 消费者 = session.createConsumer(queue); 消息消息=consumer.receive(); Queue queue = // Get the queue MessageConsumer consumer = session.createConsumer(queue); Message message = consumer.receive(); 此时,消息仅在消费者的事务视图中被消费。但对于其他拥有自己事务观点的消费者来说,该消息仍然可用。 At this point, the message has only been consumed in the consumer's transactional view. But to other consumers with their own transactional views, the message is still available. 会话.commit(); session.commit(); 现在,假设提交消息不会引发任何异常,消费者的事务视图将成为消息系统的视图,消息系统现在会考虑所消费的消息。 Now, assuming that the commit message does not throw any exceptions, the consumer's transactional view becomes the message system's, which now considers the message consumed. |
|
示例: .NET 事务队列 Example: .NET Transactional Queue 在 .NET 中,默认情况下队列不是事务性的,因此要使用事务性客户端,必须在创建队列时将其设置为事务性的: In .NET, queues are not transactional by default, so to use a transactional client, the queue must be made transactional when it is created: MessageQueue.Create("MyQueue", true);
MessageQueue.Create("MyQueue", true);
一旦队列是事务性的,队列上的每个客户端操作(发送或接收)就可以是事务性的或非事务性的。事务性接收看起来像这样: Once a queue is transactional, each client action (send or receive) on the queue can be transactional or nontransactional. A transactional Receive looks like this: MessageQueue 队列 = new MessageQueue("MyQueue");
MessageQueueTransaction 事务 =
新的消息队列事务();
交易.Begin();
消息消息=queue.Receive(transaction);
事务.Commit();
MessageQueue queue = new MessageQueue("MyQueue");
MessageQueueTransaction transaction =
new MessageQueueTransaction();
transaction.Begin();
Message message = queue.Receive(transaction);
transaction.Commit();
尽管客户端已收到消息,但消息系统并未使该消息在队列上不可用,直到客户端成功提交事务[ SysMsg ]。 Although the client had received the message, the messaging system did not make the message unavailable on the queue until the client committed the transaction successfully [SysMsg]. |
|
示例: 使用 MSMQ 的事务过滤器 Example: Transactional Filter with MSMQ 以下示例增强了管道和过滤器中引入的基本过滤器组件以使用事务。此示例实现了发送-接收消息对场景,在同一事务内接收和发送消息。我们实际上只需添加几行代码即可使过滤器具有事务性。我们使用 MessageQueueTransaction 类型的变量来管理事务。我们在消费输入消息之前打开一个事务并在我们发布输出消息后提交。如果发生任何异常,我们将中止事务,这将回滚所有消息消费和发布操作,并将输入消息返回到队列以供其他队列使用者使用。 The following example enhances the basic filter component introduced in Pipes and Filters to use transactions. This example implements the Send-Receive Message Pair scenario, receiving and sending a message inside the same transaction. We really have to add only a few lines of code to make the filter transactional. We use a variable of type MessageQueueTransaction to manage the transaction. We open a transaction before we consume the input message and commit after we publish the output message. If any exception occurs, we abort the transaction, which rolls back all message consumption and publication actions and returns the input message to the queue to be available to other queue consumers. 公共类事务过滤器 { 受保护的消息队列输入队列; 受保护的消息队列输出队列; 受保护的线程接收线程; 受保护的布尔stopFlag = false; 公共事务过滤器(消息队列输入队列, public class TransactionalFilter { protected MessageQueue inputQueue; protected MessageQueue outputQueue; protected Thread receiveThread; protected bool stopFlag = false; public TransactionalFilter (MessageQueue inputQueue, MessageQueue outputQueue) { this.inputQueue = inputQueue; this.inputQueue.Formatter = new System.Messaging .XmlMessageFormatter (new String[] {"System .String,mscorlib"}); this.outputQueue = outputQueue; } public void Process() { ThreadStart receiveDelegate = new ThreadStart(this .ReceiveMessages); receiveThread = new Thread(receiveDelegate); receiveThread.Start(); } private void ReceiveMessages() { MessageQueueTransaction myTransaction = new MessageQueueTransaction(); while (!stopFlag) { try { myTransaction.Begin(); Message inputMessage = inputQueue.Receive (myTransaction); Message outputMessage = ProcessMessage(inputMessage); outputQueue.Send(outputMessage, myTransaction); myTransaction.Commit(); } catch (Exception e) { Console.WriteLine(e.Message + " - Transaction aborted "); myTransaction.Abort(); } } } protected virtual Message ProcessMessage(Message m) { Console.WriteLine("Received Message: " + m.Body); return m; } } 我们如何验证我们的交易客户端是否按预期工作?我们创建基本TransactionalFilter 类的子类,恰当地命名为RandomlyFailingFilter。对于每条消费的消息,此过滤器都会抽取一个 0 到 10 之间的随机数。如果该数字小于 3,它会抛出任意异常(对于示例来说,ArgumentNullException 似乎足够方便)。如果我们在管道和过滤器中描述的基本非事务过滤器之上实现此过滤器,我们将丢失大约三分之一的消息。 How do we verify that our Transactional Client works as intended? We create a subclass of the basic TransactionalFilter class, the aptly named class RandomlyFailingFilter. For each consumed message, this filter draws a random number between 0 and 10. If the number is less than 3, it throws an arbitrary exception (ArgumentNullException seemed convenient enough for an example). If we implemented this filter on top of our basic, nontransactional filter described in Pipes and Filters, we would lose about one in three messages. 公共类RandomlyFailingFilter:TransactionalFilter { 随机兰特 = new Random(); 公共RandomlyFailingFilter(消息队列输入队列, public class RandomlyFailingFilter : TransactionalFilter { Random rand = new Random(); public RandomlyFailingFilter(MessageQueue inputQueue, MessageQueue outputQueue) : base (inputQueue, outputQueue) { } protected override Message ProcessMessage(Message m) { string text = (string)m.Body; Console.WriteLine("Received Message: " + text); if (rand.Next(10) < 3) { Console.WriteLine("EXCEPTION"); throw (new ArgumentNullException()); } if (text == "end") stopFlag = true; return(m); } } 为了确保我们不会丢失事务版本的任何消息,我们安装了一个简单的测试工具,它将一系列消息发布到输入队列,并确保它可以从输出队列以正确的顺序接收所有消息。重要的是要记住,只有当我们运行事务过滤器的单个实例时,输出消息才会保持顺序。如果我们并行运行多个过滤器,消息可能(并且将会)乱序(请参阅Resequencer )。 To make sure that we do not lose any messages with the transactional version, we rigged up a simple test harness that publishes a sequence of messages to the input queue and makes sure that it can receive all messages in the correct order from the output queue. It is important to remember that the output messages remain in sequence only if we run a single instance of the transactional filter. If we run multiple filters in parallel, messages can (and will) get out of order (see Resequencer ). 公共无效运行测试() { MessageQueueTransaction myTransaction = 新 public void RunTests() { MessageQueueTransaction myTransaction = new MessageQueueTransaction(); for (int i=0; i < messages.Length; i++) { myTransaction.Begin(); inQueue.Send(messages[i], myTransaction); myTransaction.Commit(); } for (int i=0; i < messages.Length; i++) { myTransaction.Begin(); Message message = outQueue.Receive(new TimeSpan(0,0,3), myTransaction); myTransaction.Commit(); String text = (String)message.Body; Console.Write(text); if (text == messages[i]) Console.WriteLine(" OK"); else Console.WriteLine(" ERROR"); } Console.WriteLine("Hit enter to exit"); Console.ReadLine(); } |
应用程序需要使用Messages ,但它希望控制何时使用每条消息。
An application needs to consume Messages, but it wants to control when it consumes each message.
|
当应用程序准备就绪时,应用程序如何使用消息? How can an application consume a message when the application is ready? |
消息消费者退出的原因之一是消费消息。消息代表需要完成的工作,因此消费者需要使用这些消息并完成工作。
Message consumers exit for one reasonto consume messages. The messages represent work that needs to be done, so the consumer needs to consume those messages and do the work.
但是消费者如何知道新消息何时可用?最简单的方法是消费者重复检查通道以查看消息是否可用。当消息可用时,它会消耗该消息,然后返回检查下一条消息。这个过程称为轮询。
But how does the consumer know when a new message is available? The easiest approach is for the consumer to repeatedly check the channel to see if a message is available. When a message is available, it consumes the message and then goes back to checking for the next one. This process is called polling.
轮询的优点在于消费者可以在准备好接收下一条消息时请求下一条消息。因此,它以它想要的速率而不是消息到达通道的速率消耗消息。
The beauty of polling is that the consumer can request the next message when it is ready for another message. So, it consumes messages at the rate it wants to rather than at the rate they arrive in the channel.
|
应用程序应该使用轮询消费者,它在想要接收消息时显式进行调用。 The application should use a Polling Consumer, one that explicitly makes a call when it wants to receive a message. |
这也称为同步接收器,因为接收器线程会阻塞,直到收到消息。我们将其称为轮询消费者,因为接收者轮询消息,处理它,然后轮询另一个消息。为了方便起见,消息传递 API 通常提供一个接收方法,该方法会一直阻塞,直到消息被传递为止,此外还有receiveNoWait()和Receive(0)等在没有可用消息时立即返回的方法。仅当接收方轮询速度快于消息到达速度时,这种差异才会明显。
This is also known as a synchronous receiver, because the receiver thread blocks until a message is received. We call it a Polling Consumer because the receiver polls for a message, processes it, then polls for another. As a convenience, messaging APIs usually provide a receive method that blocks until a message is delivered in addition to methods like receiveNoWait() and Receive(0) that return immediately if no message is available. This difference is only apparent when the receiver is polling faster than messages are arriving.
轮询使用者是应用程序用来通过显式请求来接收消息的对象。当应用程序准备好接收另一条消息时,它会轮询消费者,消费者又从消息传递系统获取消息并将其返回。(消费者如何从消息传递系统获取消息是特定于实现的,可能涉及也可能不涉及轮询。应用程序所知道的是,在明确请求消息之前它不会收到消息。)
A Polling Consumer is an object that an application uses to receive messages by explicitly requesting them. When the application is ready for another message, it polls the consumer, which in turn gets a message from the messaging system and returns it. (How the consumer gets the message from the messaging system is implementation-specific and may or may not involve polling. All the application knows is that it doesn't get the message until it explicitly asks for one.)
轮询消费者序列
Polling Consumer Sequence
当应用程序轮询消息时,使用者会阻塞,直到收到要返回的消息(或直到满足某些其他条件,例如时间限制)。应用程序收到消息后,就可以对其进行处理。一旦处理完消息并希望接收另一条消息,应用程序就可以再次轮询。
When the application polls for a message, the consumer blocks until it gets a message to return (or until some other condition is met, such as a time limit). Once the application receives the message, it can process it. Once it is through processing the message and wishes to receive another, the application can poll again.
通过使用轮询消费者,应用程序可以通过限制轮询的线程数量来控制并发消费的消息数量。这有助于防止接收应用程序因过多的请求而不堪重负;额外的消息会排队等待接收者可以处理它们。
By using Polling Consumers, an application can control how many messages are consumed concurrently by limiting the number of threads that are polling. This can help keep the receiving application from being overwhelmed by too many requests; extra messages queue up until the receiver can process them.
接收器应用程序通常对每个希望监视的通道(至少)使用一个线程,但它也可以使用单个线程来监视多个通道,这有助于在监视经常为空的通道时节省线程。要轮询单个通道,假设线程在消息到达之前没有任何事情可做,请使用在消息到达之前阻塞的receive版本。要使用单个线程轮询多个通道,或者在等待消息到达时执行其他工作,请使用带有超时或receiveNoWait()的 receive 版本,以便如果一个通道为空,线程将继续检查另一个通道或执行其他工作。
A receiver-application typically uses one thread (at least) per channel that it wishes to monitor, but it can also use a single thread to monitor multiple channels, which helps conserve threads when monitoring channels that are frequently empty. To poll a single channel, assuming that the thread has nothing to do until a message arrives, use a version of receive that blocks until a message arrives. To poll multiple channels with a single thread, or to perform other work while waiting for a message to arrive, use a version of receive with a timeout or receiveNoWait() so that if one channel is empty, the thread goes on to check another channel or perform other work.
轮询过多或阻塞线程时间过长的消费者可能效率低下,在这种情况下,事件驱动的消费者可能会更高效。多个轮询消费者可以是竞争消费者。消息调度程序可以实现为轮询消费者。轮询消费者可以是选择性消费者;它也可以是持久订阅者。轮询消费者也可以是事务客户端,以便消费者可以控制消息何时从通道中实际删除。
A consumer that is polling too much or blocking threads for too long can be inefficient, in which case an Event-Driven Consumer may be more efficient. Multiple Polling Consumers can be Competing Consumers. A Message Dispatcher can be implemented as a Polling Consumer. A Polling Consumer can be a Selective Consumer; it can also be a Durable Subscriber. A Polling Consumer can also be a Transactional Client so that the consumer can control when the message is actually removed from the channel.
|
示例: JMS 接收 Example: JMS Receive 在 JMS 中,消息使用者使用MessageConsumer.receive同步消费消息 [ JMS 1.1], [ Hapner ] 。 In JMS, a message consumer uses MessageConsumer.receive to consume a message synchronously [JMS 1.1], [Hapner]. MessageConsumer 具有三种不同的接收方法: MessageConsumer has three different receive methods:
例如,创建消费者并接收消息的代码非常简单: For example, the code to create a consumer and receive a message is very simple: 目的地 dest = // 获取目的地 Session session = // 创建会话 MessageConsumer 消费者 = session.createConsumer(dest); 消息消息=consumer.receive(); Destination dest = // Get the destination Session session = // Create the session MessageConsumer consumer = session.createConsumer(dest); Message message = consumer.receive(); |
|
示例: .NET 接收 Example: .NET Receive 在.NET中,消费者使用MessageQueue.Receive来同步消费消息[ SysMsg ] 。 In .NET, a consumer uses MessageQueue.Receive to consume a message synchronously [SysMsg]. MessageQueue客户端有多种接收方式。最简单的两个是 A MessageQueue client has several variations of receive. The two simplest are
从现有队列接收消息的代码非常简单: The code to receive a message from an existing queue is quite simple: MessageQueue queue = // 获取队列 消息消息=queue.Receive(); MessageQueue queue = // Get the queue Message message = queue.Receive(); |
应用程序需要在消息传递后立即使用它们。
An application needs to consume Messages as soon as they're delivered.
|
应用程序如何在消息可用时自动使用消息? How can an application automatically consume messages as they become available? |
轮询消费者的问题在于,当通道为空时,消费者会在轮询不存在的消息时阻塞线程和/或消耗进程时间。轮询使客户端能够控制消耗速度,但当没有东西可消耗时会浪费资源。
The problem with Polling Consumers is that when the channel is empty, the consumer blocks threads and/or consumes process time while polling for messages that are not there. Polling enables the client to control the rate of consumption but wastes resources when there's nothing to consume.
与其不断询问通道是否有消息要使用,不如通道能够告诉客户端何时有消息可用。因此,不要让消费者轮询消息,而是在消息可用时立即将消息提供给消费者。
Rather than continuously asking the channel if it has messages to consume, it would be better if the channel could tell the client when a message is available. For that matter, instead of making the consumer poll for the message, just give the message to the consumer as soon as the message becomes available.
|
应用程序应该使用事件驱动的消费者,它是一种在消息在通道上传递时自动传递消息的消费者。 The application should use an Event-Driven Consumer, one that is automatically handed messages as they're delivered on the channel. |
这也称为异步接收器,因为在回调线程传递消息之前接收器没有正在运行的线程。我们将其称为事件驱动的消费者,因为接收者的行为就像消息传递是一个触发接收者采取行动的事件。
This is also known as an asynchronous receiver, because the receiver does not have a running thread until a callback thread delivers a message. We call it an Event-Driven Consumer because the receiver acts like the message delivery is an event that triggers the receiver into action.
事件驱动消费者是当消息到达消费者的通道时由消息传递系统调用的对象。消费者通过应用程序 API 中的回调将消息传递给应用程序。(消息传递系统如何获取消息是特定于实现的,并且可能是也可能不是事件驱动的。消费者所知道的是,它可以在没有活动线程的情况下处于休眠状态,直到消息传递系统调用它并向其传递消息。)
An Event-Driven Consumer is an object that is invoked by the messaging system when a message arrives on the consumer's channel. The consumer passes the message to the application through a callback in the application's API. (How the messaging system gets the message is implementation-specific and may or may not be event-driven. All the consumer knows is that it can sit dormant with no active threads until it gets invoked by the messaging system passing it a message.)
事件驱动的消费者由消息传递系统调用,但它会调用特定于应用程序的回调。为了弥补这一差距,消费者拥有一个特定于应用程序的实现,该实现符合消息传递系统定义的已知 API。
An Event-Driven Consumer is invoked by the messaging system, yet it invokes an application-specific callback. To bridge this gap, the consumer has an application-specific implementation that conforms to a known API defined by the messaging system.
事件驱动消费者的代码由两部分组成:
The code for an Event-Driven Consumer consists of two parts:
初始化 应用程序创建一个特定于应用程序的使用者并将其与特定的消息通道关联起来。这段代码运行一次后,消费者就准备好接收一系列消息。
Initialization The application creates an application-specific consumer and associates it with a particular Message Channel. After this code is run once, the consumer is ready to receive a series of messages.
消费者 接收消息并将其传递给应用程序进行处理。此代码针对每个正在使用的消息运行一次。
Consumption The consumer receives a message and passes it to the application for processing. This code is run once per message being consumed.
事件驱动的消费者序列
Event-Driven Consumer Sequence
应用程序创建其自定义使用者并将其与通道关联。一旦消费者被初始化,它(和应用程序)就可以进入休眠状态,没有正在运行的线程,等待消息到达时被调用。
The application creates its custom consumer and associates it with the channel. Once the consumer is initialized, it (and the application) can then go dormant, with no running threads, waiting to be invoked when a message arrives.
当消息被传递时,消息传递系统调用消费者的消息接收事件方法并将消息作为参数传递。消费者使用应用程序的回调 API 将消息传递给应用程序。应用程序现在已收到消息并可以对其进行处理。一旦应用程序完成消息处理,它和消费者就可以再次休眠,直到下一条消息到达。通常,消息传递系统不会通过单个使用者运行多个线程,因此使用者一次只能处理一条消息。
When a message is delivered, the messaging system calls the consumer's message-received-event method and passes in the message as a parameter. The consumer passes the message to the application, using the application's callback API. The application now has the message and can process it. Once the application finishes processing the message, it and the consumer can then go dormant again until the next message arrives. Typically, a messaging system will not run multiple threads through a single consumer, so the consumer can process only one message at a time.
事件驱动的 Consumer在消息可用时自动使用它们。为了更细粒度地控制消费率,请使用Polling Consumer 。事件驱动的消费者可以是竞争的消费者。消息调度程序可以实现为事件驱动的消费者。事件驱动的消费者可以是选择性消费者;它也可以是持久订阅者。事务性客户端与事件驱动消费者的配合可能不如与轮询消费者的配合好;请参阅 JMS 示例。
Event-Driven Consumers automatically consume messages as they become available. For more fine-grained control of the consumption rate, use a Polling Consumer. Event-Driven Consumers can be Competing Consumers. A Message Dispatcher can be implemented as an Event-Driven Consumer. An Event-Driven Consumer can be a Selective Consumer; it can also be a Durable Subscriber. Transactional Clients may not work as well with Event-Driven Consumers as they do with Polling Consumers; see the JMS example.
|
示例: JMS 消息监听器 Example: JMS MessageListener 在 JMS 中,事件驱动消费者是一个实现MessageListener接口 [Hapner ] 的类。该接口声明一个方法onMessage(Message)。消费者实现onMessage来处理消息。以下是 JMS 执行者的示例: In JMS, an Event-Driven Consumer is a class that implements the MessageListener interface [Hapner]. This interface declares a single method, onMessage(Message). The consumer implements onMessage to process the message. Here is an example of a JMS performer: 公共类 MyEventDrivenConsumer 实现 MessageListener {
公共无效onMessage(消息消息){
// 处理消息
}
}
public class MyEventDrivenConsumer implements MessageListener {
public void onMessage(Message message) {
// Process the message
}
}
事件驱动消费者的初始化部分创建所需的执行者对象(这是一个MessageListener实例)并将其与所需通道的消息消费者关联: The initializer part of an Event-Driven Consumer creates the desired performer object (which is a MessageListener instance) and associates it with a message consumer for the desired channel: 目的地目的地 = // 获取目的地 Session session = // 创建会话 MessageConsumer 消费者 = session.createConsumer(destination); MessageListener 监听器 = new MyEventDrivenConsumer(); 消费者.setMessageListener(监听器); Destination destination = // Get the destination Session session = // Create the session MessageConsumer consumer = session.createConsumer(destination); MessageListener listener = new MyEventDrivenConsumer(); consumer.setMessageListener(listener); 现在,当消息传递到目标时,JMS 提供程序将调用MyEventDrivenConsumer.onMessage,并将消息作为参数。 Now, when a message is delivered to the destination, the JMS provider will call MyEventDrivenConsumer.onMessage with the message as a parameter. 请注意,在 JMS 中,同时也是事务客户端的事件驱动消费者将无法按预期工作。通常,当事务中的代码抛出异常时,事务会回滚,但MessageListener.onMessage签名不提供抛出异常(例如JMSException ),并且运行时异常被视为程序员错误。如果发生运行时异常,JMS 提供者将通过传递下一条消息进行响应,因此导致异常的消息会丢失 [ JMS 1.1 ]、[ Hapner ]。要成功实现事务性、事件驱动的行为,请使用消息驱动的 EJB [ EJB 2.0],[哈普纳]。 Note that in JMS, an Event-Driven Consumer that is also a Transactional Client will not work as expected. Normally, a transaction is rolled back when the code in the transaction throws an exception, but the MessageListener.onMessage signature does not provide for an exception being thrown (such as JMSException), and a runtime exception is considered programmer error. If a runtime exception occurs, the JMS provider responds by delivering the next message, so the message that caused the exception is lost [JMS 1.1], [Hapner]. To successfully achieve transactional, event-driven behavior, use a message-driven EJB [EJB 2.0], [Hapner]. |
|
示例: .NET ReceiveCompletedEventHandler Example: .NET ReceiveCompletedEventHandler 在 .NET 中,事件驱动消费者的执行者部分实现了一个ReceiveCompletedEventHandler 委托方法。此委托方法必须接受两个参数:一个是 MessageQueue 的对象,另一个是来自ReceiveCompletedReceiveCompletedEventArgs 。 该方法使用参数从队列中获取消息并对其进行处理。以下是 .NET 执行者的示例: With .NET, the performer part of an Event-Driven Consumer implements a method that is a ReceiveCompletedEventHandler delegate. This delegate method must accept two parameters: an object that is the MessageQueue and a ReceiveCompletedEventArgs that is the arguments from the ReceiveCompleted event [SysMsg]. The method uses the arguments to get the message from the queue and process it. Here is an example of a .NET performer: 公共静态无效MyEventDrivenConsumer(对象源,
ReceiveCompletedEventArgs asyncResult)
{
消息队列 mq = (消息队列) 源;
消息 m = mq.EndReceive(asyncResult.AsyncResult);
// 处理消息
mq.BeginReceive();
返回;
}
public static void MyEventDrivenConsumer(Object source,
ReceiveCompletedEventArgs asyncResult)
{
MessageQueue mq = (MessageQueue) source;
Message m = mq.EndReceive(asyncResult.AsyncResult);
// Process the message
mq.BeginReceive();
return;
}
事件驱动客户端的初始化程序部分指定队列应运行委托方法来处理ReceiveCompleted 事件: The initializer part of an event-driven client specifies that the queue should run the delegate method to handle a ReceiveCompleted event: MessageQueue queue = // 获取队列
队列.ReceiveCompleted +=
新的 ReceiveCompletedEventHandler(MyEventDrivenConsumer);
队列.BeginReceive();
MessageQueue queue = // Get the queue
queue.ReceiveCompleted +=
new ReceiveCompletedEventHandler(MyEventDrivenConsumer);
queue.BeginReceive();
现在,当消息传递到队列时,队列将发出ReceiveCompleted 事件,该事件将运行MyEventDrivenConsumer方法。 Now, when a message is delivered to the queue, the queue will issue a ReceiveCompleted event, which will run the MyEventDrivenConsumer method. |
应用程序正在使用消息传递。但是,它处理消息的速度无法与将消息添加到通道的速度一样快。
An application is using Messaging. However, it cannot process messages as fast as they're being added to the channel.
|
消息客户端如何同时处理多条消息? How can a messaging client process multiple messages concurrently? |
消息按顺序通过消息通道到达,因此消费者的自然倾向是按顺序处理它们。然而,顺序消费可能太慢,并且消息可能在通道上堆积,这使得消息系统成为瓶颈并损害应用程序的整体吞吐量。发生这种情况的原因可能是通道上有多个发送方、网络中断导致消息积压,然后一次性全部传递、接收方中断导致积压、或者每条消息需要花费更多的精力来消费和执行。它确实可以创建和发送。
Messages arrive through a Message Channel sequentially, so the natural inclination of a consumer is to process them sequentially. However, sequential consumption may be too slow, and messages may pile up on the channel, which makes the messaging system a bottleneck and hurts overall throughput of the application. This can happen either because of multiple senders on the channel, because a network outage causes a backlog of messages which are then delivered all at once, because a receiver outage causes a backlog, or because each message takes significantly more effort to consume and perform than it does to create and send.
应用程序可以使用多个通道,但一个通道可能成为瓶颈,而另一个通道则空着,并且发送者不知道要使用哪一个等效通道。然而,多个通道的优点是允许多个消费者(每个通道一个)同时处理消息。即使这有效,应用程序定义的通道数量仍然会限制吞吐量。
The application could use multiple channels, but one channel might become a bottleneck while another sits empty, and a sender would not know which one of equivalent channels to use. Multiple channels would have the advantage, however, of enabling multiple consumers (one per channel), processing messages concurrently. Even if this worked, though, the number of channels the application defined would still limit the throughput.
我们需要一种让渠道拥有多个消费者的方法。
What is needed is a way for a channel to have multiple consumers.
|
在单个通道上创建多个竞争消费者,以便消费者可以同时处理多个消息。 Create multiple Competing Consumers on a single channel so that the consumers can process multiple messages concurrently. |
竞争消费者是多个消费者,它们都是为了从单个点对点通道接收消息而创建的。当通道传递消息时,任何消费者都可能收到它。消息传递系统的实现决定了哪个消费者实际接收消息,但实际上消费者相互竞争成为接收者。一旦消费者收到消息,它就可以委托给其应用程序的其余部分来帮助处理该消息。(此解决方案仅适用于点对点通道;发布-订阅通道上的多个消费者只需为每条消息创建更多副本。)
Competing Consumers are multiple consumers that are all created to receive messages from a single Point-to-Point Channel. When the channel delivers a message, any of the consumers could potentially receive it. The messaging system's implementation determines which consumer actually receives the message, but in effect the consumers compete with each other to be the receiver. Once a consumer receives a message, it can delegate to the rest of its application to help process the message. (This solution only works with Point-to-Point Channels; multiple consumers on a Publish-Subscribe Channel just create more copies of each message.)
竞争消费者序列
Competing Consumers Sequence
每个竞争消费者都在自己的线程中运行,以便它们都可以同时消费消息。当通道传递消息时,消息传递系统的事务控制确保只有一个消费者成功接收该消息。当该消费者正在处理消息时,通道可以传递其他消息,其他消费者可以同时使用和处理这些消息。通道协调消费者,确保每个消费者收到不同的消息;消费者不必相互协调。
Each of the Competing Consumers runs in its own thread so that they all can consume messages concurrently. When the channel delivers a message, the messaging system's transactional controls ensure that only one of the consumers successfully receives the message. While that consumer is processing the message, the channel can deliver other messages, which other consumers can concurrently consume and process. The channel coordinates the consumers, making sure that each receives a different message; the consumers do not have to coordinate with each other.
每个消费者同时处理不同的消息,因此瓶颈变成了通道向消费者提供消息的速度,而不是消费者处理消息需要多长时间。有限数量的消费者可能仍然是瓶颈,但只要有可用的计算资源,增加消费者数量就可以缓解该限制。
Each consumer processes a different message concurrently, so the bottleneck becomes how quickly the channel can feed messages to the consumers instead of how long it takes a consumer to process a message. A limited number of consumers may still be a bottleneck, but increasing the number of consumers can alleviate that constraint as long as there are available computing resources.
要并发运行,每个使用者必须在自己的线程中运行。对于轮询消费者来说,这意味着每个消费者必须有自己的线程来同时执行轮询。对于事件驱动的消费者,消息系统必须为每个并发消费者使用一个线程;该线程将用于将消息传递给消费者,并由消费者用来处理消息。
To run concurrently, each consumer must run in its own thread. For Polling Consumers, this means that each consumer must have its own thread to perform the polling concurrently. For Event-Driven Consumers, the messaging system must use a thread per concurrent consumer; that thread will be used to hand the message to the consumer and will be used by the consumer to process the message.
复杂的消息传递系统将检测通道上的竞争消费者,并在内部提供消息调度程序,以确保每条消息仅传递给单个消费者。这有助于避免多个消费者都认为自己是单个消息的消费者时出现的冲突。不太复杂的消息系统将允许多个消费者尝试使用同一条消息。当这种情况发生时,无论哪个消费者首先提交其交易,都会获胜;那么其他消费者将无法成功提交,并且必须回滚他们的事务。
A sophisticated messaging system will detect competing consumers on a channel and internally provide a Message Dispatcher that ensures that each message is only delivered to a single consumer. This helps avoid conflicts that would arise if multiple consumers each thought they were the consumer of a single message. A less sophisticated messaging system will allow multiple consumers to attempt to consume the same message. When this happens, whichever consumer commits its transaction first wins; then the other consumers will not be able to commit successfully and will have to roll back their transactions.
允许多个消费者尝试使用同一消息的消息传递系统可能会使事务客户端效率非常低。客户端认为它有一条消息,使用它,花费精力处理该消息,然后尝试提交但无法提交(因为该消息已被竞争对手使用)。频繁执行工作只是为了回滚会损害吞吐量,而此解决方案的目的是提高吞吐量。因此,应仔细衡量竞争性交易消费者的表现;它可能因不同的消息传递系统实现和配置而有很大差异。
A messaging system that allows multiple consumers to attempt consuming the same message can make a Transactional Client very inefficient. The client thinks it has a message, consumes it, spends effort processing the message, then tries to commit and cannot (because the message has already been consumed by a competitor). Frequently performing work just to roll it back hurts throughput, whereas the point of this solution is to increase throughput. Thus, the performance of competing transactional consumers should be measured carefully; it could vary significantly on different messaging system implementations and configurations.
竞争消费者不仅可以用于在单个应用程序中的多个消费者线程之间分散负载;它们还可以将消耗负载分散到多个应用程序(例如进程)。这样,如果一个应用程序无法足够快地消费消息,则多个消费者应用程序(可能每个应用程序都使用多个消费者线程)可以解决该问题。使用多个线程在多台计算机上运行多个应用程序来消费消息的能力提供了几乎无限的消息处理能力,其中唯一的限制是消息传递系统将消息从通道传递给消费者的能力。
Not only can Competing Consumers be used to spread load across multiple consumer threads in a single application; they can also spread the consumption load across multiple applications (e.g., processes). This way, if one application cannot consume messages fast enough, multiple consumer applicationsperhaps with each employing multiple consumer threadscan attack the problem. The ability to have multiple applications running on multiple computers using multiple threads to consume messages provides virtually unlimited message processing capacity, where the only limit is the messaging system's ability to deliver messages from the channel to the consumers.
竞争消费者的协调取决于每个消息系统的实现。如果客户端想要自己实现这种协调,则应该使用消息调度程序。竞争消费者可以是轮询消费者、事件驱动消费者或其组合。竞争的事务客户端可能会浪费大量精力来处理其接收操作未成功提交且必须回滚的消息。
The coordination of competing consumers depends on each messaging system's implementation. If the client wants to implement this coordination itself, it should use a Message Dispatcher. Competing Consumers can be Polling Consumers, Event-Driven Consumers, or a combination thereof. Competing Transactional Clients can waste significant effort processing messages whose receive operations do not commit successfully and have to be rolled back.
|
示例: 简单的 JMS 竞争消费者 Example: Simple JMS Competing Consumers 这是一个如何用 Java 实现竞争消费者的简单示例。外部驱动程序/管理器对象(未显示)运行其中的几个。它在自己的线程中运行每个线程,并调用stopRunning()使其停止。 This is a simple example of how to implement a competing consumer in Java. An external driver/manager object (not shown) runs a couple of them. It runs each one in its own thread and calls stopRunning() to make it stop. JMS 会话必须是单线程的 [ JMS 1.1 ]、[ Hapner ]。单个会话序列化消息消费的顺序 [JMS 11],[ Hapner ]。因此,为了使每个竞争消费者在自己的线程中正常工作,并且为了使消费者能够并行消费消息,每个消费者必须有自己的Session (因此也有自己的MessageConsumer ) 。JMS 规范没有指定并发QueueReceiver(例如,竞争消费者)应该有效,甚至要求这种方法完全有效。因此,使用此技术的应用程序不被认为是可移植的,并且可能与不同的 JMS 提供者 [ JMS 1.1 ]、[ Hapner ] 一起以不同的方式工作。 A JMS session must be single-threaded [JMS 1.1], [Hapner]. A single session serializes the order of message consumption [JMS 11], [Hapner]. So, for each competing consumer to work properly in its own thread, and for the consumers to be able to consume messages in parallel, each consumer must have its own Session (and therefore its own MessageConsumer). The JMS specification does not specify the semantics of how concurrent QueueReceivers (e.g., Competing Consumers) should work, or even require that this approach work at all. Thus, applications that use this technique are not assumed to be portable and may work differently with different JMS providers [JMS 1.1], [Hapner]. 消费者类实现Runnable,以便可以在自己的线程中运行;这允许消费者同时运行。所有消费者共享相同的Connection ,但每个消费者都创建自己的Session ,这很重要,因为每个会话只能支持单个线程。每个消费者重复从队列接收消息并处理它。 The consumer class implements Runnable so that it can run in its own thread; this allows the consumers to run concurrently. All of the consumers share the same Connection, but each creates its own Session, which is important, since each session can only support a single thread. Each consumer repeatedly receives a message from the queue and processes it. 导入 javax.jms.Connection; 导入 javax.jms.Destination; 导入 javax.jms.JMSException; 导入javax.jms.Message; 导入 javax.jms.MessageConsumer; 导入javax.jms.Session; 导入 javax.naming.NamingException; 公共类 CompetingConsumer 实现 Runnable { 私有 int 执行者ID; 私有MessageConsumer消费者; 私有布尔值正在运行; 受保护的竞争消费者() { 极好的(); } 公共静态CompetingConsumer newConsumer(int id, import javax.jms.Connection; import javax.jms.Destination; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.Session; import javax.naming.NamingException; public class CompetingConsumer implements Runnable { private int performerID; private MessageConsumer consumer; private boolean isRunning; protected CompetingConsumer() { super(); } public static CompetingConsumer newConsumer(int id, Connection connection, String queueName) throws JMSException, NamingException { CompetingConsumer consumer = new CompetingConsumer(); consumer.initialize(id, connection, queueName); return consumer; } protected void initialize(int id, Connection connection, String queueName) throws JMSException, NamingException { performerID = id; Session session = connection.createSession(false, Session .AUTO_ACKNOWLEDGE); Destination dispatcherQueue = JndiUtil.getDestination (queueName); consumer = session.createConsumer(dispatcherQueue); isRunning = true; } public void run() { try { while (isRunning()) receiveSync(); } catch (Exception e) { e.printStackTrace(); } } private synchronized boolean isRunning() { return isRunning; } public synchronized void stopRunning() { isRunning = false; } private void receiveSync() throws JMSException, InterruptedException { Message message = consumer.receive(); if (message != null) processMessage(message); } private void processMessage(Message message) throws JMSException, InterruptedException { int id = message.getIntProperty("cust_id"); System.out.println(System.currentTimeMillis() + ": Performer #" + performerID + " starting; message ID " + id); Thread.sleep(500); System.out.println(System.currentTimeMillis() + ": Performer #" + performerID + " processing."); Thread.sleep(500); System.out.println(System.currentTimeMillis() + ": Performer #" + performerID + " finished."); } } 因此,实现一个简单的竞争消费者很容易。主要技巧是让消费者成为Runnable并在自己的线程中运行它。 So, implementing a simple Competing Consumer is easy. The main trick is to make the consumer a Runnable and run it in its own thread. |
应用程序正在使用消息传递。应用程序需要单个消息通道上的多个使用者以协调的方式工作。
An application is using Messaging. The application needs multiple consumers on a single Message Channel to work in a coordinated fashion.
|
单个通道上的多个消费者如何协调他们的消息处理? How can multiple consumers on a single channel coordinate their message processing? |
单个点对点通道上的多个消费者充当竞争消费者。当消费者可以互换时,这很好,但它不允许专门化消费者,以便某些消费者能够更好地消费某些消息。
Multiple consumers on a single Point-to-Point Channel act as Competing Consumers. That's fine when the consumers are interchangeable, but it does not allow for specializing the consumers so that certain consumers are better able to consume certain messages.
单个发布-订阅通道上的多个消费者将无法按预期工作。这些消费者不会分配消息负载,而是重复工作。
Multiple consumers on a single Publish-Subscribe Channel won't work as intended. Rather than distribute the message load, these consumers will duplicate the effort.
选择性消费者可以但是,并非所有消息系统都支持此功能。即使在那些这样做的人中,他们也可能不支持基于消息正文中的值的选择。它们的选择器值表达式可能太简单而无法充分区分消息,或者重复评估这些表达式的性能可能很慢。可能有许多表达式需要以协调的方式仔细设计,以便它们不会重叠,但也不会留下任何未处理的选择器值。他们可能需要为其他使用者未处理或意外的选择器值实现默认情况。
Selective Consumers can be used as specialized consumers. However, not all messaging systems support this feature. Even among those that do, they may not support selection based on values in the body of the message. Their selector value expressions may be too simple to adequately distinguish among the messages, or the performance of repeatedly evaluating those expressions may be slow. There may be numerous expressions that need to be carefully designed in a coordinated fashion so that they do not overlap but also do not leave any selector values unhandled. They may need to implement a default case for selector values that are not handled by other consumers or that are unexpected.
数据类型通道可但类型系统可能太大且多样化,无法证明为每种类型创建单独的通道是合理的。或者,类型可能基于动态变化的标准,这很难用静态通道集来处理。企业可能已经需要大量的通道,这会给消息传递系统带来负担,并且为不同的消息类型增加许多这些通道可能只是需要太多的通道。
Datatype Channels can be used to keep different types of messages separate and to enable consumers to specialize for those message types. But the type system may be too large and varied to justify creating a separate channel for each type. Or, the types may be based on dynamically changing criteria, which are difficult to handle with a static set of channels. The enterprise may already require a huge number of channels, taxing the messaging system, and multiplying many of those channels for distinct message types may simply require too many channels.
如果消费者共同努力,这些问题都可以得到解决。他们可以通过了解其他消费者是否已经处理了该工作来避免重复工作。他们可以是专业化的;如果一个消费者收到了与其专业相关的错误消息,它可以将该消息传递给另一个具有正确专业的消费者。如果应用程序有太多通道进入,它可以通过让所有消费者共享一个通道来节省通道;他们将进行协调,以确保将正确的信息传达给正确的消费者。
Each of these problems could be solved if consumers could work together. They could avoid duplicating work by being aware if another consumer had already processed that work. They could be specialized; if a consumer got the wrong kind of message for its specialty, it could hand off the message to another consumer with the right specialty. If an application had too many channels coming in, it could save channels by having all of its consumers share a single channel; they would coordinate to make sure that the right messages went to the right consumers.
唉,消费者是非常独立的对象,很难协调。让专门的消费者足够通用来处理任何消息并将其传递给每个消费者会增加大量的设计和处理开销。他们都必须互相了解,以便可以分派工作,并且他们都需要知道其他人中哪些人在忙,以免消费者在处理另一条消息时向其提供要处理的消息。让消费者一起工作将从根本上改变典型的消费者设计。
Alas, consumers are very independent objects that are difficult to coordinate. Making specialized consumers general enough to handle any message and hand it off would add a lot of design and processing overhead to each consumer. They would all have to know about each other so they could hand off work, and they would all need to know which of the others were busy so as not to give a consumer a message to process while it's already processing another. Making consumers work together would radically change the typical consumer design.
中介者模式[GoF ]提供了一些帮助。中介者协调一组对象,以便它们不需要知道如何相互协调。我们需要一个中介者来协调通道的消费者。然后,每个消费者可以专注于处理特定类型的消息,而协调器可以确保正确的消息到达正确的消费者。
The Mediator pattern [GoF] offers some help. A Mediator coordinates a group of objects so that they don't need to know how to coordinate with each other. What we need for messaging is a mediator that coordinates the consumers for a channel. Then, each consumer could focus on processing a particular kind of message, and the coordinator could make sure the right message gets to the right consumer.
|
在通道上创建一个消息调度程序,它将使用来自通道的消息并将它们分发给执行者。 Create a Message Dispatcher on a channel that will consume messages from a channel and distribute them to performers. |
消息调度程序由两部分组成:
A Message Dispatcher consists of two parts:
调度 程序使用来自通道的消息并将每条消息分发给执行者的对象。
Dispatcher The object that consumes messages from a channel and distributes each message to a performer.
Performer调度程序提供消息并处理该消息的对象。
Performer The object that is given the message by the dispatcher and processes it.
当消息调度程序接收到消息时,它会获取执行者并将消息调度给执行者进行处理。执行者可以委托给其应用程序的其余部分来帮助处理其消息。执行者可以由调度员新创建或者可以从可用执行者池中选择。每个执行者可以在自己的线程中运行以同时处理消息。所有执行者可能适合所有消息,或者调度程序可以根据消息的属性将消息与专门的执行者匹配。
When a Message Dispatcher receives a message, it obtains a performer and dispatches the message to the performer to process it. A performer can delegate to the rest of its application to help process its message. The performer could be newly created by the dispatcher or could be selected from a pool of available performers. Each performer can run in its own thread to process messages concurrently. All performers may be appropriate for all messages, or the dispatcher may match a message to a specialized performer based on properties of the message.
消息调度程序序列
Message Dispatcher Sequence
当调度程序收到消息时,它会将消息委托给可用的执行者来处理它。如果执行者使用调度程序的线程处理消息,则调度程序将阻塞,直到执行者完成对消息的处理。相反,如果执行者在自己的线程中处理消息,那么一旦调度程序启动该线程,它就可以立即开始接收其他消息并将它们委托给其他执行者,以便并发处理消息。这样,消息的消耗速度与调度程序接收和委托消息的速度一样快,而不管每条消息的处理时间有多长。
When the dispatcher receives a message, it delegates the message to an available performer to process it. If the performer processes the message using the dispatcher's thread, then the dispatcher blocks until the performer is finished processing the message. Conversely, if the performer processes the message in its own thread, then once the dispatcher starts that thread, it can immediately start receiving other messages and delegating them to other performers so that the messages are processed concurrently. This way, messages can be consumed as fast as the dispatcher can receive and delegate them, regardless of how long each message takes to process.
调度程序充当单个通道和一组执行者之间的一对多连接。表演者完成了大部分工作;调度程序只是充当匹配者,将每条消息与可用的执行者进行匹配,并且只要执行者在自己的线程中运行,就不会阻塞。调度程序接收消息,然后将其发送给执行者进行处理。由于调度程序所做的工作相对较少并且不会阻塞,因此它可以像消息传递系统提供消息一样快地调度消息,从而避免成为瓶颈。
A dispatcher acts as a one-to-many connection between a single channel and a group of performers. The performers do most of the work; the dispatcher just acts as a matchmaker, matching each message with an available performer, and does not block as long as the performers run in their own threads. The dispatcher receives the message and then sends it to a performer to process it. Because the dispatcher does relatively little work and does not block, it potentially can dispatch messages as fast as the messaging system can feed them and thus avoids becoming a bottleneck.
该模式是反应器模式[ POSA2 ]的一个更简单的、特定于消息传递的版本,其中消息调度程序是反应器,消息执行者是具体事件处理程序。消息通道充当同步事件多路分解器,使调度程序一次可以使用一条消息。消息本身就像句柄,但简单得多。真正的句柄往往是对资源数据的引用,而消息通常直接包含数据。(但是,消息不必直接存储数据。如果消息的数据存储在外部,并且消息是 Claim Check,则消息包含对数据的引用,这更像是反应器句柄。)不同类型的句柄选择不同类型的具体事件处理程序,而消息通道是数据类型通道,因此所有消息(句柄)都属于同一类型,并且通常只有一种类型的具体事件处理程序。
This pattern is a simpler, messaging-specific version of the Reactor pattern [POSA2], where the message dispatcher is a Reactor and the message performers are Concrete Event Handlers. The Message Channel acts as the Synchronous Event Demultiplexer, making the messages available to the dispatcher one at a time. The messages themselves are like Handles, but much simpler. A true handle tends to be a reference to a resource's data, whereas a message usually contains the data directly. (However, the message does not have to store the data directly. If a message's data is stored externally and the message is a Claim Check, then the message contains a reference to the data, which is more like a Reactor handle.) Different types of handles select different types of concrete event handlers, whereas a Message Channel is a Datatype Channel, so all of the messages (handles) are of the same type, and there is typically only one type of concrete event handler.
数据类型通道设计一个通道,以便所有消息都属于同一类型,并且所有消费者都处理该类型的消息,而反应堆模式指出了使用消息调度程序在同一通道上支持多种数据类型并使用特定于类型的方式处理它们的机会。表演者。每条消息必须指定其类型;调度程序检测消息的类型并将其调度到特定于类型的执行者进行处理。通过这种方式,具有专门执行者的调度程序可以充当数据类型通道的替代方案以及选择性消费者的专门实现。
Whereas Datatype Channel designs a channel so that all messages are of the same type and all consumers process messages of that type, the Reactor pattern points out an opportunity to use Message Dispatcher to support multiple datatypes on the same channel and process them with type-specific performers. Each message must specify its type; the dispatcher detects the message's type and dispatches it to a type-specific performer for processing. In this way, a dispatcher with specialized performers can act as an alternative to Datatype Channels and as a specialized implementation of Selective Consumers.
消息调度程序和竞争消费者之间的区别之一是跨多个应用程序分发的能力。尽管一组竞争消费者可能分布在多个进程(例如,应用程序)中,但一组执行者通常都在与调度程序相同的进程中运行(即使它们在不同的线程中运行)。如果执行者与其调度程序运行在不同的进程中,则调度程序必须以分布式远程过程调用方式与执行者进行通信,而这正是消息传递首先要避免的。
One difference between Message Dispatcher and Competing Consumers is the ability to distribute across multiple applications. Whereas a set of Competing Consumers may be distributed among multiple processes (e.g., applications), a set of performers typically all run in the same process as the dispatcher (even if they run in different threads). If a performer were running in a different process from its dispatcher, the dispatcher would have to communicate with the performer in a distributed, Remote Procedure Invocation manner, which is exactly what Messaging intends to avoid in the first place.
由于调度程序是单个消费者,因此它可以与点对点通道和发布-订阅通道配合良好。通过点对点消息传递,调度程序可以成为竞争消费者的合适替代方案;如果消息传递系统不能很好地处理多个消费者,或者如果跨不同消息传递系统实现的多个消费者的处理不一致,则这种替代方案可能是更可取的。
Since a dispatcher is a single consumer, it works fine with both Point-to-Point Channels and Publish-Subscribe Channels. With point-to-point messaging, a dispatcher can be a suitable alternative to Competing Consumers; this alternative may be preferable if the messaging system handles multiple consumers badly or if handling of multiple consumers across different messaging system implementations is inconsistent.
调度程序使执行者的工作方式与事件驱动的消费者非常相似,即使调度程序本身可以是事件驱动的或轮询消费者。因此,将调度程序实现为事务客户端的一部分可能很困难。如果客户端是事务性的,则理想情况下,调度程序应允许执行者在完成事务之前处理消息。然后,只有当执行者成功时,调度程序才应提交事务。如果执行者无法处理消息,则调度程序应回滚事务。由于每个执行者可能需要回滚其单独的消息,因此调度程序需要每个执行者的会话,并且必须使用该执行者的会话来接收执行者的消息并完成其事务。由于事件驱动的消费者通常不能很好地与事务性客户端一起工作,因此调度程序不应该是事件驱动的消费者,而应该是轮询消费者。
A dispatcher makes the performers work much like Event-Driven Consumers, even though the dispatcher itself could be event-driven or a Polling Consumer. As such, implementing a dispatcher as part of a Transactional Client can be difficult. If the client is transactional, ideally the dispatcher should allow the performer to process a message before completing the transaction. Then, only if the performer is successful should the dispatcher commit the transaction. If the performer fails to process the message, the dispatcher should roll back the transaction. Since each performer may need to roll back its individual message, the dispatcher needs a session for each performer and must use that performer's session to receive the performer's message and complete its transaction. Since Event-Driven Consumers often do not work well with Transactional Clients, the dispatcher should not be an Event-Driven Consumer, but rather should be a Polling Consumer.
将执行者实现为事件驱动的消费者会很有帮助。在 JMS 中,这意味着将执行者实现为MessageListener 。消息监听器有一个方法,onMessage(Message);它接受消息并执行任何必要的处理。这在调度员和执行者之间形成了清晰的分离。同样,在 .NET 中,执行者应该是ReceiveCompletedEventHandler 委托,即使调度程序不会真正发出ReceiveCompleted 事件。然而,这些事件驱动的方法可能与在其自己的线程中运行执行者所需的 API 不兼容。
It can be helpful to implement performers as Event-Driven Consumers. In JMS, this means implementing the performer as a MessageListener. A message listener has one method, onMessage(Message); it accepts a message and performs whatever processing necessary. This forms a clean separation between the dispatcher and the performer. Likewise, in .NET, the performer should be a ReceiveCompletedEventHandler delegate, even though the dispatcher will not really issue ReceiveCompleted events. However, these event-driven approaches may not be compatible with the API necessary to run a performer in its own thread.
为了避免实现您自己的消息调度程序,请考虑在数据类型通道上使用竞争消费者或使用选择性消费者。消息调度程序可以是轮询消费者或事件驱动消费者。消息调度程序并不能成为非常好的事务客户端。
To avoid the effort of implementing your own Message Dispatcher, consider instead using Competing Consumers on a Datatype Channel or using Selective Consumers. A Message Dispatcher can be a Polling Consumer or an Event-Driven Consumer. A Message Dispatcher does not make a very good Transactional Client.
|
示例: .NET 调度程序 Example: .NET Dispatcher 通常,消息调度程序将消息调度给执行者(请参阅 Java 示例)。.NET 提供了另一种选择:调度程序可以使用 Peek 来检测消息并获取其消息 ID,然后将消息 ID(不是完整消息)调度给执行者。然后,执行者使用ReceiveById 来消费已分配给它的特定消息。通过这种方式,每个执行者不仅可以负责处理消息,还可以负责消费消息,这可以帮助解决并发问题,特别是当消费者是事务性客户端时。 Usually, a Message Dispatcher dispatches messages to the performers (see the Java example). .NET provides another option: The dispatcher can use Peek to detect a message and get its message ID, then dispatch the message ID (not the full message) to the performer. The performer then uses ReceiveById to consume the particular message it has been assigned. In this way, each performer can take responsibility not just for processing the message but for consuming it as well, which can help with concurrency issues, especially when the consumers are Transactional Clients. |
应用程序正在使用消息传递。它消耗来自消息通道的消息,但它不一定要消耗该通道上的所有消息,而只是其中的一些消息。
An application is using Messaging. It consumes Messages from a Message Channel, but it does not necessarily want to consume all of the messages on that channeljust some of them.
|
消息消费者如何选择它希望接收哪些消息? How can a message consumer select which messages it wishes to receive? |
默认情况下,如果消息通道只有一个消费者,则该通道上的所有消息都将传递给该消费者。同样,如果通道上有多个竞争消费者,则任何消息都可能发送给任何消费者,并且每条消息都将发送给某个消费者。消费者通常无法选择消费哪些消息;它总是收到下一条消息。
By default, if a Message Channel has only one consumer, all Messages on that channel will be delivered to that consumer. Likewise, if there are multiple Competing Consumers on the channel, any message can potentially go to any consumer, and every message will go to some consumer. A consumer normally does not get to choose which messages it consumes; it always gets whatever message is next.
只要消费者想要接收通道上的任何和所有消息(通常是这种情况),这种行为就可以。然而,当消费者只想消费某些消息时,这是一个问题,因为消费者通常无法控制它在通道上接收哪些消息。为什么消费者只想接收某些消息?考虑一个处理贷款请求消息的应用程序;它可能希望以不同于 100,000 美元以上的方式处理 100,000 美元以下的贷款。一种方法是让应用程序拥有两种不同类型的消费者,一种用于小额贷款,另一种用于大额贷款。然而,由于任何消费者都可以接收任何消息,应用程序如何确保将正确的消息发送给正确的消费者?
This behavior is fine as long as the consumer wants to receive any and all messages on the channel, which is normally the case. This is a problem, however, when a consumer wants to consume only certain messages, because a consumer normally has no control over which messages on a channel it receives. Why would a consumer want to receive only certain messages? Consider an application processing loan request messages; it may want to process loans for up to $100,000 differently from those over $100,000. One approach would be for the application to have two different kinds of consumers, one for small loans and another for big loans. Yet, since any consumer can receive any message, how can the application make sure that the right messages go to the right consumer?
最简单的方法可能是每个人都消费它收到的任何消息。如果它收到错误类型的消息,它可以以某种方式将该消息传递给适当类型的消费者。不过,这会很困难;消费者实例通常彼此不了解,并且找到尚未忙于处理另一条消息的消费者实例可能很困难。也许当消费者意识到它不需要该消息时,它可以将消息放回到通道上。但随后它可能会再次使用该消息。也许每个消费者都可以获得每条消息的副本,并丢弃不需要的消息。这会起作用,但会导致大量消息重复以及对最终被丢弃的消息的大量浪费处理。
The simplest approach might be for each to consume whatever messages it gets. If it gets the wrong kind of message, it could somehow hand that message to the appropriate kind of consumer. That's going to be difficult, though; consumer instances usually don't know about each other, and finding one that isn't already busy processing another message can be difficult. Perhaps when the consumer realizes it doesn't want the message, it could put the message back on the channel. But then it's likely to just consume the message yet again. Perhaps every consumer could get a copy of every message and just discard the ones it doesn't want. This will work but will cause a lot of message duplication and a lot of wasted processing on messages that are ultimately discarded.
也许消息传递系统可以为每种类型的消息定义单独的通道。然后,发送者可以确保在正确的通道上发送每条消息,而接收者可以确保他们从特定通道接收到的消息是所需的类型。然而,这个解决方案不是很动态。接收者可能会在系统运行时更改其选择标准,这将需要定义新的通道并重新分发通道上已有的消息。它还意味着发送者必须知道接收者的选择标准是什么以及这些标准何时改变。标准必须是接收者的属性,而不是通道的属性,并且通道上的消息需要指定它们满足什么标准。
Perhaps the messaging system could define separate channels for each type of message. Then, the sender could make sure to send each message on the proper channel, and the receivers could be sure that the messages they receive off of a particular channel are the kind desired. However, this solution is not very dynamic. The receivers may change their selection criteria while the system is running, which would require defining new channels and redistributing the messages already on the channels. It also means that the senders must know what the receivers' selection criteria are and when those criteria change. The criteria need to be a property of the receivers, not of the channels, and the messages on the channel need to specify what criteria they meet.
我们需要一种方法,让符合各种标准的消息都在同一个通道上发送,让消费者能够指定他们感兴趣的标准,并且让每个消费者只接收满足其要求的消息。标准。
What is needed is a way for messages fitting a variety of criteria to all be sent on the same channel, for the consumers to be able to specify what criteria they're interested in, and for each consumer to receive only the messages that meet its criteria.
|
让消费者成为选择性消费者,它会过滤其通道传递的消息,以便只接收符合其条件的消息。 Make the consumer a Selective Consumer, one that filters the messages delivered by its channel so that it receives only the ones that match its criteria. |
此过滤过程分为三个部分:
There are three parts to this filtering process:
指定生产者 在发送消息之前 指定消息的选择值。
Specifying Producer Specifies the message's selection value before sending it.
选择值 消息中指定的一个或多个值,允许消费者决定是否选择该消息。
Selection Value One or more values specified in the message that allow a consumer to decide whether to select the message.
选择性消费者 仅接收符合其选择标准的消息。
Selective Consumer Only receives messages that meet its selection criteria.
消息发送者在发送之前指定每条消息的选择值。当消息到达时,选择性消费者会测试消息的选择值,以查看该值是否满足消费者的选择标准。如果是,消费者接收消息并将其传递给应用程序进行处理。
The message sender specifies each message's selection value before sending it. When a message arrives, a Selective Consumer tests the message's selection value to see if the value meets the consumer's selection criteria. If so, the consumer receives the message and passes it to the application for processing.
选择性消费序列
Selective Consumer Sequence
当发送者创建消息时,它也会设置消息的选择值;然后它发送消息。当消息系统传递消息时,选择性消费者测试消息的选择值以确定是否选择该消息。如果消息通过,使用者将接收消息并使用回调将消息传递给应用程序。
When the sender creates the message, it also sets the message's selection value; then it sends the message. When the messaging system delivers the message, the Selective Consumer tests the message's selection value to determine whether to select the message. If the message passes, the consumer receives the message and passes the message to the application, using a callback.
Selective Consumer通常用于组中,一个消费者过滤一组条件,而另一个消费者过滤另一组条件,依此类推。对于贷款处理示例,一个消费者会选择“金额 < = $100,000”,而另一个消费者会选择“金额 > $100,000”。然后,每个消费者只能获得其感兴趣的贷款类型。
Selective Consumers are often used in groupsone consumer filters for one set of criteria, while another filters for a different set, and so on. For the loan processing example, one consumer would select "amount < = $100,000" while another would select "amount > $100,000." Then, each consumer would only get the kinds of loans it is interested in.
当多个选择性消费者与点对点时,它们实际上成为同样具有选择性的竞争消费者。如果两个消费者的标准重叠,并且消息的选择值满足两个消费者的标准,则任一消费者都可以消费该消息。消费者的设计应确保其中至少一个有资格消费每一个有效的选择值。否则,具有不匹配选择值的消息将永远不会被消耗,并且将永远混乱通道(或至少直到发生消息过期为止)。
When multiple Selective Consumers are used with a Point-to-Point Channel, they effectively become Competing Consumers that are also selective. If two consumers' criteria overlap, and a message's selection value meets both of their criteria, either consumer can consume the message. Consumers should be designed to ensure that at least one of them is eligible to consume every valid selection value. Otherwise, a message with an unmatched selection value will never be consumed and will clutter the channel forever (or at least until Message Expiration occurs).
当多个选择性消费者与发布-订阅通道一起使用时,每条消息都将传递给每个订阅者,但订阅者将简单地忽略不符合其条件的消息副本。一旦消费者决定忽略消息,消息传递系统就可以丢弃该消息,因为它已成功传递并且永远不会被使用。消息传递系统可以通过甚至不传递它知道消费者会忽略的消息来优化此过程,从而减少必须生成和传输的消息副本的数量。这种丢弃被忽略消息的行为与任何保证交付、持久订阅者无关和/或使用消息过期设置。
When multiple Selective Consumers are used with a Publish-Subscribe Channel, each message will be delivered to each subscriber, but a subscriber will simply ignore its copy of a message that does not fit its criteria. Once a consumer decides to ignore a message, the messaging system can discard the message, since it has been successfully delivered and will never be consumed. A messaging system can optimize this process by not even delivering a message it knows the consumer will ignore, thereby decreasing the number of copies of a message that must be produced and transmitted. This behavior of discarding ignored messages is independent of whatever Guaranteed Delivery, Durable Subscriber, and/or Message Expiration settings are used.
Selective Consumer使单个通道的行为类似于多个数据类型通道。不同类型的消息可以具有不同的选择值,以便专门用于特定类型的消费者将仅接收该类型的消息。这可以促进使用少量通道发送大量类型。此方法还可以在需要的通道数量超出消息传递系统所能支持的企业中保留通道。
Selective Consumers make a single channel act like multiple Datatype Channels. Different types of messages can have different selection values so that a consumer that is specialized for a particular type will only receive messages of that type. This can facilitate sending a large number of types using a small number of channels. This approach can also conserve channels in an enterprise that requires more channels than a messaging system can support.
竞争性的、选择性的消费者
Competing, Selective Consumers
当尝试向某些消费者应用程序隐藏某种类型的消息时,使用Selective Consumer来模拟数据类型通道并不是一个好方法。虽然消息传递系统可以确保只有授权的应用程序成功地从通道接收消息,但它们通常不会授权消费者的选择标准,因此被授权访问该通道的恶意消费者可以通过更改其标准来访问未经授权的消息。需要单独的数据类型通道来安全地锁定应用程序。
Using Selective Consumers to emulate Datatype Channels is not a good approach when trying to hide messages of a certain type from certain consumer applications. Whereas a messaging system can ensure only authorized applications successfully receive messages from a channel, they usually do not authorize a consumer's selection criteria, so a malicious consumer authorized to access the channel can gain access to unauthorized messages by changing its criteria. Separate datatype channels are needed to securely lock out applications.
使用选择性消费者的替代方法是使用消息调度程序。选择标准内置于调度程序中,然后调度程序使用它们来确定每条消息的执行者。如果消息不满足执行者的任何标准,调度程序可以将不匹配的消息重新路由到无效消息通道,而不是让它混乱通道或丢弃它。 与消息调度程序和竞争消费者之间的权衡一样,问题实际上是您希望让消息传递系统进行调度还是希望自己实现它。如果消息系统不支持选择性消费者作为一项功能,您别无选择,只能使用Message Dispatcher 自行实现。
An alternative to using Selective Consumers is to use a Message Dispatcher. The selection criteria are built into the dispatcher, which then uses them to determine the performer for each message. If a message does not meet any of the performer's criteria, rather than leave it cluttering the channel or discarding it, the dispatcher can reroute the unmatched message to the Invalid Message Channel. As with the trade-off between Message Dispatcher and Competing Consumers, the question is really whether you wish to let the messaging system do the dispatching or you want to implement it yourself. If a messaging system does not support Selective Consumer as a feature, you have no choice but to implement it yourself using a Message Dispatcher.
正如前面提到的,如果通道上的Selective Consumer都没有与消息的选择器值匹配,则该消息将被忽略,就像该通道没有接收者一样。过程编程中的一个类似问题是 case 语句,其中没有一个 case 与正在测试的值匹配。因此,case 语句可以具有与任何其他 case 都不匹配的值相匹配的默认 case。将这种方法应用于消息传递,为没有匹配消费者的消息创建某种默认消费者似乎很诱人。然而,这样的默认使用者将无法按预期工作,因为它需要一个与所有选择器值匹配的表达式,因此它将与所有其他使用者竞争。相反,要实现默认消费者,请使用消息调度程序针对
As mentioned earlier, if none of the Selective Consumers on a channel match the message's selector value, the message will be ignored as if the channel has no receivers. A similar problem in procedural programming is a case statement where none of the cases match the value being tested. Thus, a case statement can have a default case that matches values that aren't matched by any other case. Applying this approach to messaging, it may seem tempting to create some sort of default consumer for messages that otherwise have no matching consumer. Yet, such a default consumer will not work as desired because it would require an expression that matches all selector values, so it would compete with all other consumers. Instead, to implement a default consumer, use a Message Dispatcher that implements a case statement with a default option for unhandled cases that uses the default consumer.
选择性消费者的另一个替代方案是消息过滤器。他们实现了大致相同的目标,但方式不同。使用选择性消费者,所有消息都会传递给接收者,但每个接收者都会忽略不需要的消息。消息过滤器位于发送方的通道和接收方的通道之间,仅将所需的消息从发送方的通道传输到接收方的通道。因此,不需要的消息甚至永远不会传递到接收者的通道,因此接收者没有什么可以忽略的。消息过滤器对于删除接收者不想要的消息非常有用。选择性消费者当一个接收者想要忽略某些消息但其他接收者想要接收这些消息时,s 非常有用。
Another alternative to Selective Consumers is Message Filters. They accomplish much the same goal, but in different ways. With a Selective Consumer, all of the messages are delivered to the receivers, but each receiver ignores unwanted messages. A Message Filter sits between a channel from the sender and a channel to the receiver and only transfers desired messages from the sender's channel to the receiver's. Thus, unwanted messages are never even delivered to the receiver's channel, so the receiver has nothing to ignore. Message Filter is useful for getting rid of messages that no receiver wants. Selective Consumers are useful when one receiver wants to ignore certain messages but other receivers want to receive those messages.
另一个值得考虑的替代方案是基于内容的路由器。这种类型的路由器就像过滤器一样,确保通道仅获取接收者想要的消息,这可以提高安全性并提高消费者的性能。然而,选择性消费者更加灵活,因为每个过滤选项只需要一个新的消费者(在系统运行时很容易创建),而每个新选项都带有一个基于内容路由器需要一个新的输出通道(在系统运行时创建和使用并不那么容易)以及新通道的新消费者。考虑一项要求变更,您希望以不同于小型和大型贷款的方式处理中型贷款(50,000 美元到 150,000 美元)。使用基于内容的路由器,您需要为中等贷款创建一个新渠道,以及该新渠道上的消费者,并调整路由器分离贷款的方式。您还需要担心更改生效后会发生什么,因为一些已经路由到原始通道的消息可能尚未被消费,现在可能位于错误的通道上。与选择性消费者,您只需将两种类型的消费者(小于 100,000 美元和大于 100,000 美元)替换为三种类型(小于 50,000 美元、50,000 美元至 150,000 美元和大于 150,000 美元)。基于内容的路由器是一种更加静态的方法,而选择性消费者则更加动态。
Another alternative to consider is Content-Based Router. This type of router, like a filter, makes sure that a channel gets only the messages that the receivers want, which can increase security and increase the performance of the consumers. Selective Consumer is more flexible, however, because each filtering option simply requires a new consumer (which is easy to create while the system is running), whereas each new option with a Content-Based Router requires a new output channel (which is not so easy to create and use while the system is running) as well as a new consumer for the new channel. Consider a requirements change where you want to process medium-size loans ($50,000 to $150,000) differently from small and large loans. With Content-Based Router, you need to create a new channel for medium loans, as well as a consumer on that new channel, and adjust the way the router separates loans. You also need to worry about what happens when the change takes effect, because some messages that have already been routed onto the original channels may not have been consumed yet and may now be on the wrong channel. With Selective Consumer, you just replace the two types of consumers (less than $100,000 and greater than $100,000) with three types (less than $50,000, $50,000 to $150,000, and greater than $150,000). Content-Based Router is a much more static approach, whereas Selective Consumer can be much more dynamic.
理想情况下,消息的选择值应在其标头中指定,而不是其正文,以便选择性消费者可以处理该值,而无需解析(并知道如何解析)消息正文。
Ideally, a message's selection value should be specified in its header, not its body, so that a Selective Consumer can process the value without having to parse (and know how to parse) the message's body.
Selective Consumer使单个通道的行为类似于多个数据类型通道。它们允许消息可供其他接收者使用,而消息过滤器可防止将不需要的消息传递给任何接收者,并且它们可以比基于内容的路由器更动态地使用。选择性消费者可以实现为轮询消费者或事件驱动消费者,并且可以是事务。要自己实现过滤行为,请使用消息调度程序。
Selective Consumer s make a single channel act like multiple Datatype Channels. They allow messages to be available for other receivers, whereas Message Filter prevents unwanted messages from being delivered to any receiver, and they can be used more dynamically than a Content-Based Router. A Selective Consumer can be implemented as a Polling Consumer or Event-Driven Consumer and can be part of a Transactional Client. To implement the filtering behavior yourself, use a Message Dispatcher.
|
示例: 分离类型 Example: Separating Types 渠道数量有限的股票交易系统可能需要使用一个渠道进行报价和交易。执行报价的接收者与交易的接收者非常不同,因此正确的接收者需要确保使用正确的消息。发送者将报价消息上的选择器值设置为 QUOTE,并且报价的选择性消费者将仅消费具有该选择器值的消息。贸易消息将具有其发送者和接收者将使用的自己的 TRADE 选择器值。这样,两种消息类型可以成功地共享单个通道。 A stock trading system with a limited number of channels might need to use one channel for both quotes and trades. The receiver for performing a quote is very different from that for trading, so the right receiver needs to be sure to consume the right message. The sender would set the selector value on a quote message to QUOTE, and the Selective Consumer for quotes would consume only messages with that selector value. Trade messages would have their own TRADE selector value that their senders and receivers would use. In this way, two message types can successfully share a single channel. |
|
示例: JMS 消息选择器 Example: JMS Message Selector 在 JMS 中,可以使用消息选择器创建字符串MessageConsumer( QueueReceiver或 TopicSubscriber),该消息选择器字符串根据属性值 [JMS 1.1]、[ Hapner]过滤消息。 首先,发送者在消息中设置接收者可以过滤的属性值: In JMS, a MessageConsumer (QueueReceiver or TopicSubscriber) can be created with a message selector string that filters messages based on their property values [JMS 1.1], [Hapner]. First, a sender sets the value of a property in the message that the receiver could filter by: Session session = // 获取会话
TextMessage 消息 = session.createTextMessage();
message.setText("<quote>SUNW</quote>");
message.setStringProperty("req_type", "quote");
目的地目的地= //获取目的地
MessageProducer 生产者 = session.createProducer(destination);
生产者.发送(消息);
Session session = // get the session
TextMessage message = session.createTextMessage();
message.setText("<quote>SUNW</quote>");
message.setStringProperty("req_type", "quote");
Destination destination = //get the destination
MessageProducer producer = session.createProducer(destination);
producer.send(message);
其次,接收者设置其消息选择器来过滤该值: Second, a receiver sets its message selector to filter for that value: Session session = // 获取会话
目的地目的地= //获取目的地
字符串选择器 = "req_type = 'quote'";
消息消费者消费者 =
session.createConsumer(目的地, 选择器);
Session session = // get the session
Destination destination = //get the destination
String selector = "req_type = 'quote'";
MessageConsumer consumer =
session.createConsumer(destination, selector);
此接收方将忽略其请求类型属性未设置为引用的所有消息,就好像这些消息根本从未传递到目的地一样。 This receiver will ignore all messages whose request type property is not set to quote as if those messages were never delivered to the destination at all. |
|
示例: .NET Peek、ReceiveById 和 ReceiveByCorrelationId Example: .NET Peek, ReceiveById, and ReceiveByCorrelationId 在 .NET 中,MessageQueue.Receive本身不支持 JMS 样式的消息选择器。相反,接收者可以做的是使用MessageQueue.Peek来查看消息。如果它满足所需的条件,则可以使用MessageQueue.Receive从队列中读取它。不过,这可能工作得不太可靠,因为Receive调用返回的消息不一定与所查看的消息相同。因此,使用ReceiveById ,消费者由此指定其希望接收的消息的 ID 属性值(而不是指定 Receive),以确保获取与所查看的消息相同的消息。 In .NET, MessageQueue.Receive does not support JMS-style message selectors per se. Rather, what a receiver can do is use MessageQueue.Peek to look at a message. If it meets the desired criteria, then it can use MessageQueue.Receive to read it from the queue. This may not work very reliably, though, since the message returned by the Receive call may not necessarily be the same message that was peeked. Thus, use ReceiveById, whereby the consumer specifies the ID property value of the message it wishes to receive (instead of specifying Receive) to ensure getting the same message that was peeked. .NET 中的另一个选项是ReceiveByCorrelationId方法,使用者可以使用该方法指定其想要接收的消息的CorrelationId 属性值。特定请求消息的发送者可以使用ReceiveByCorrelationId 来接收特定于该请求的回复消息(请参阅请求回复和相关标识符) 。 Another option in .NET is the ReceiveByCorrelationId method with which the consumer specifies the CorrelationId property value of the message it wants to receive. A sender of a particular request message can use ReceiveByCorrelationId to receive the reply message specific to that request (see Request-Reply and Correlation Identifier ). |
应用程序正在发布-订阅通道上接收消息。
An application is receiving messages on a Publish-Subscribe Channel.
|
订户如何避免在未收听消息时丢失消息? How can a subscriber avoid missing messages while it's not listening for them? |
为什么这甚至是一个问题?将消息添加到通道后,它会一直保留在那里,直到被消耗、过期(请参阅消息过期)或系统崩溃(除非您使用保证传递) 。对于点对点通道上的消息来说确实如此,但发布-订阅通道的工作方式略有不同。
Why is this even an issue? Once a message is added to a channel, it stays there until it is either consumed, it expires (see Message Expiration ), or the system crashes (unless you're using Guaranteed Delivery ). This is true for a message on a Point-to-Point Channel, but a Publish-Subscribe Channel works somewhat differently.
当消息在发布-订阅通道上发布时,消息传递系统必须将消息传递给每个订阅者。它如何做到这一点是特定于实现的:它可以保留消息,直到尚未收到消息的订阅者列表为空,或者它可能会复制消息并将消息传递给每个订阅者。无论哪种情况,哪些订阅者接收消息完全取决于发布消息时谁订阅了该频道。如果消息发布时接收者没有订阅,那么即使接收者稍后订阅,也不会收到该消息。(还有一个时间问题,当订阅者订阅并且消息“大约”同时在同一频道上发布时,会发生什么情况。订阅者是否收到消息?如何解决这个问题取决于消息系统的实现。为了安全起见,订阅者应确保在发布感兴趣的消息之前订阅。)
When a message is published on a Publish-Subscribe Channel, the messaging system must deliver the message to each subscriber. How it does this is implementation specific: It can keep the message until the list of subscribers that have not received it is empty, or it might duplicate and deliver the message to each subscriber. Whatever the case, which subscribers receive the message is completely dependent upon who is subscribed to the channel when the message is published. If a receiver is not subscribed when the message is published, even if the receiver subscribes an instant later, it will not receive that message. (There is also a timing issue of what happens when a subscriber subscribes and a message is published on the same channel at "about" the same time. Does the subscriber receive the message? How this issue is resolved depends on the messaging system's implementation. To be safe, subscribers should be sure to subscribe before messages of interest are published.)
实际上,订阅者通过关闭与频道的连接来取消订阅频道。因此,不需要明确的取消订阅操作;订阅者只需关闭其连接。
As a practical matter, a subscriber unsubscribes from a channel by closing its connection to the channel. Thus, no explicit unsubscribe action is necessary; the subscriber just closes its connection.
通常,应用程序更愿意忽略断开连接后发布的消息,因为断开连接意味着应用程序对可能发布的任何内容不感兴趣。例如,销售积木的 B2B/C 应用程序可以订阅买家可以请求积木的频道。如果应用程序停止销售砖块,或者暂时没有砖块,它可能会决定断开与通道的连接,以避免收到无论如何都无法满足的请求。
Often, an application prefers to ignore messages published after it disconnects, because being disconnected means that the application is uninterested in whatever may be published. For example, a B2B/C application selling bricks may subscribe to a channel where buyers can request bricks. If the application stops selling bricks, or is temporarily out of bricks, it may decide to disconnect from the channel to avoid receiving requests it cannot fulfill anyway.
然而,这种行为可能是不利的,因为“你打瞌睡,你就放松了”的方法可能会导致应用程序错过它需要的消息。如果应用程序崩溃或必须停止进行维护,它可能想知道在不运行时错过了哪些消息。消息传递的整体理念是即使发送者和接收者应用程序和网络不同时工作,也使通信可靠。
Yet this behavior can be disadvantageous, because the "you snooze, you loose" approach can cause an application to miss messages it needs. If an application crashes or must be stopped for maintenance, it may want to know what messages it missed while it wasn't running. The whole idea of messaging is to make communication reliable even if the sender and receiver applications and network aren't all working at the same time.
因此,有时应用程序会断开连接,因为它们不再需要来自该通道的消息。但有时应用程序必须短时间断开连接,当它们重新连接时,它们希望能够访问在连接失效期间发布的所有消息。订阅者通常处于连接(已订阅)或断开连接(取消订阅)状态,但第三种可能的状态是inactive ,即订阅者已断开连接但仍处于订阅状态,因为它希望接收在断开连接时发布的消息。
So, sometimes applications disconnect because they don't want messages from that channel anymore. But sometimes applications have to disconnect for a short time, and when they reconnect, they want to have access to all of the messages that were published during the connection lapse. A subscriber is normally either connected (subscribed) or disconnected (unsubscribed), but a third possible state is inactive, the state of a subscriber that is disconnected but still subscribed because it wants to receive messages published while it is disconnected.
如果订阅者曾经连接到发布-订阅通道,但在发布消息时断开连接,消息系统如何知道是否为订阅者保存消息,以便订阅者重新连接时可以传递消息?也就是说,消息传递系统如何知道断开连接的订阅者是不活动还是取消订阅?需要有两种订阅,一种订阅在订阅者断开连接时结束,另一种订阅即使在应用程序断开连接时仍然存在,并且仅在应用程序显式取消订阅时才中断。
If a subscriber was connected to a Publish-Subscribe Channel but is disconnected when a message is published, how does the messaging system know whether or not to save the message for the subscriber so that it can deliver the message when the subscriber reconnects? That is, how does the messaging system know whether a disconnected subscriber is inactive or unsubscribed? There needs to be two kinds of subscriptions, those that end when the subscriber disconnects and those that survive even when the application disconnects and are broken only when the application explicitly unsubscribes.
默认情况下,订阅仅在其连接期间有效。因此,我们需要另一种类型的订阅,它可以通过变得不活动而在断开连接时继续存在。
By default, a subscription lasts only as long as its connection. So, what is needed is another type of subscription that survives disconnects by becoming inactive.
|
使用持久订阅者可以使消息系统在订阅者断开连接时保存发布的消息。 Use a Durable Subscriber to make the messaging system save messages published while the subscriber is disconnected. |
持久订阅会保存不活动订阅者的消息,并在订阅者重新连接时传递这些保存的消息。这样,订阅者即使断开连接也不会丢失任何消息。当订阅者处于活动状态(例如,已连接)时,持久订阅对订阅者或消息传送系统的行为没有影响。无论订阅是持久的还是非持久的,连接的订阅者都会采取相同的行为。不同之处在于订阅者断开连接时消息传递系统的行为方式。
A durable subscription saves messages for an inactive subscriber and delivers these saved messages when the subscriber reconnects. In this way, a subscriber does not lose any messages even though it disconnected. A durable subscription has no effect on the behavior of the subscriber or the messaging system while the subscriber is active (e.g., connected). A connected subscriber acts the same whether its subscription is durable or nondurable. The difference is in how the messaging system behaves when the subscriber is disconnected.
持久订阅者只是发布-订阅通道上的订阅者。但是,当订阅者与消息传递系统断开连接时,它就会变为非活动状态,并且消息传递系统将保存在其通道上发布的所有消息,直到它再次变为活动状态。同时,同一频道的其他订阅者可能不会持久;他们是非持久订户。
A Durable Subscriber is simply a subscriber on a Publish-Subscribe Channel. However, when the subscriber disconnects from the messaging system, it becomes inactive and the messaging system will save any messages published on its channel until it becomes active again. Meanwhile, other subscribers to the same channel may not be durable; they're nondurable subscribers.
持久订阅序列
Durable Subscription Sequence
成为订阅者,持久订阅者必须建立对频道的订阅。一旦它关闭连接,它就会变得不活动。当订阅者不活动时,发布者发布消息。如果订阅者是非持久的,它将错过此消息;但由于它是持久的,消息传递系统会为该订阅者保存该消息。当订阅者重新订阅并再次变得活跃时,消息传递系统将传递排队的消息(以及为此订阅者保存的任何其他消息)。订阅者接收消息并处理它(可能将消息委托给应用程序)。一旦订阅者处理完消息,如果它不希望接收更多消息,它将关闭其连接,再次变为非活动状态。
To be a subscriber, the Durable Subscriber must establish its subscription to the channel. Once it has, when it closes its connection, it becomes inactive. While the subscriber is inactive, the publisher publishes a message. If the subscriber were nondurable, it would miss this message; but because it is durable, the messaging system saves this message for this subscriber. When the subscriber resubscribes, becoming active once more, the messaging system delivers the queued message (and any others saved for this subscriber). The subscriber receives the message and process it (perhaps delegating the message to the application). Once the subscriber is through processing messages, if it does not wish to receive any more messages, it closes its connection, becoming inactive again. Since it does not want the messaging system to save messages for it anymore, it also unsubscribes.
一个有趣的考虑是,如果持久订阅者从未取消订阅会发生什么?不活动的持久订阅将继续保留消息,即消息系统将保存所有已发布的消息,直到订阅者重新连接。但如果订阅者长时间不重新连接,保存的消息数量可能会变得过多。消息过期可以帮助缓解这个问题。消息传递系统可能还希望限制可以为非活动订阅保存的消息数量。
An interesting consideration is, What would happen if a durable subscriber never unsubscribed? The inactive durable subscription would continue to retain messagesthat is, the messaging system would save all of the published messages until the subscriber reconnects. But if the subscriber does not reconnect for a long time, the number of saved messages can become excessive. Message Expiration can help alleviate this problem. The messaging system may also wish to limit the number of messages that can be saved for an inactive subscription.
|
示例: 股票交易 Example: Stock Trading 股票交易系统可能使用发布-订阅通道来广播股票价格的变化;每当股票价格发生变化时,就会发布一条消息。一个订阅者可能是一个显示某些股票当前价格的 GUI。另一个订阅者可能是存储某些股票当天交易范围的数据库。 A stock trading system might use a Publish-Subscribe Channel to broadcast changes in stocks prices; each time a stocks' price changes, a message is published. One subscriber might be a GUI that displays the current prices for certain stocks. Another subscriber might be a database that stores the day's trading range for certain stocks. 这两个应用程序都应该是价格变化通道的订阅者,以便在股票价格变化时收到通知。GUI 的订阅可能是非持久的,因为它显示的是当前价格。如果 GUI 崩溃并失去与通道的连接,则保存 GUI 无法显示的价格更改就没有意义。相反,价格范围数据库应使用Durable Subscriber 。当它运行时,它可以显示到目前为止的范围。如果它失去连接,当它重新连接时,它可以处理发生的价格变化并根据需要更新范围。 Both applications should be subscribers to the price-change channel so that they're notified when a stock's price changes. The GUI's subscription can be nondurable because it is displaying the current price. If the GUI crashes and loses its connection to the channel, there is no point in saving price changes the GUI cannot display. Conversely, the price range database should use a Durable Subscriber. While it is running, it can display the range thus far. If it loses its connection, when it reconnects, it can process the price changes that occurred and update the range as necessary. |
|
示例: JMS 持久订阅 Example: JMS Durable Subscription JMS 支持TopicSubscribers [ JMS 1.1 ]、[ Hapner ]的持久订阅。 JMS supports durable subscriptions for TopicSubscribers [JMS 1.1], [Hapner]. 持久订阅面临的一项挑战是区分正在重新连接的旧订阅者和全新订阅者。在 JMS 中,持久订阅由三个标准来标识: One challenge with durable subscriptions is differentiating between an old subscriber that is reconnecting and a completely new subscriber. In JMS, a durable subscription is identified by three criteria:
连接的客户端 ID 是其连接工厂的一个属性,该属性是在使用消息传递系统的管理工具创建连接工厂时设置的。每个订阅者的订阅名称必须是唯一的(对于特定主题和客户端 ID)。 The connection's client ID is a property of its connection factory, which is set when the connection factory is created using the messaging system's administration tool. The subscription name has to be unique for each subscriber (for a particular topic and client ID). 使用Session.createDurableSubscriber持久订阅者: A Durable Subscriber is created using the Session.createDurableSubscriber method: ConnectionFactoryfactory = // 获取工厂 // 工厂有客户端 ID 连接连接=factory.createConnection(); // 连接与工厂具有相同的客户端 ID topic topic = // 获取主题 String clientID = connection.getClientID(); // 万一 ConnectionFactory factory = // obtain the factory // the factory has the client ID Connection connection = factory.createConnection(); // the connection has the same client ID as the factory Topic topic = // obtain the topic String clientID = connection.getClientID(); // just in case you're curious String subscriptionName = "subscriber1"; // some UID for the subscription Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); TopicSubscriber subscriber = session.createDurableSubscriber(topic, subscriptionName); 该订阅者现在处于活动状态。它将在消息发布到主题时接收消息(就像非持久订阅者一样)。要使其处于非活动状态,请将其关闭,如下所示: This subscriber is now active. It will receive messages as they are published to the topic (just like a nondurable subscriber). To make it inactive, close it, like this: 订阅者.close(); subscriber.close(); 订阅者现在已断开连接,因此处于非活动状态。发布到其主题的任何消息都将为此订阅者保存并在其重新连接时传递。 The subscriber is now disconnected and therefore inactive. Any messages published to its topic will be saved for this subscriber and delivered when it reconnects. 要使订阅再次处于活动状态,您必须创建具有相同主题、客户端 ID 和订阅名称的新持久订阅者。代码与之前相同,只是连接工厂、主题和订阅名称必须与之前相同。 To make the subscription active again, you must create a new durable subscriber with the same topic, client ID, and subscription name. The code is the same as before, except that the connection factory, topic, and subscription name must be the same as before. 由于建立持久订阅和重新连接的代码相同,因此只有消息系统知道该持久订阅是已建立还是新的。一个有趣的结果是,重新连接到订阅的应用程序可能与之前断开连接的应用程序不同。只要新应用程序与旧应用程序使用相同的主题、相同的连接工厂(以及相同的客户端 ID)和相同的订阅名称,消息系统就无法区分这两个应用程序,并将所有消息传递到旧应用程序。在断开连接之前未交付给旧应用程序的新应用程序。 Because the code is the same to establish a durable subscription and to reconnect to it, only the messaging system knows whether this durable subscription had already been established or is a new one. One interesting consequence is that the application reconnecting to a subscription may not be the same application that disconnected earlier. As long as the new application uses the same topic, the same connection factory (and so the same client ID), and the same subscription name as the old application, the messaging system cannot distinguish between the two applications and will deliver all messages to the new application that weren't delivered to the old application before it disconnected. 一旦应用程序对某个主题进行了持久订阅,它将有机会接收发布到该主题的所有消息,即使订阅者关闭了其连接(或者如果应用程序崩溃并且消息传递系统关闭了订阅者的连接)。要阻止消息传递系统为该非活动订阅者排队消息,应用程序必须显式取消订阅其持久订阅。 Once an application has a durable subscription on a topic, it will have the opportunity to receive all messages published to that topic, even if the subscriber closes its connection (or if it crashes and the messaging system closes the subscriber's connection for it). To stop the messaging system from queuing messages for this inactive subscriber, the application must explicitly unsubscribe its durable subscription. 订阅者.close(); // 订阅者现在处于非活动状态,消息将被保存 session.unsubscribe(订阅名称); // 订阅被删除 subscriber.close(); // subscriber is now inactive, messages will be saved session.unsubscribe(subscriptionName); // subscription is removed 一旦订阅者取消订阅,该订阅就会从主题中删除,并且消息将不再传递给该订阅者。 Once the subscriber is unsubscribed, the subscription is removed from the topic, and messages will no longer be delivered to this subscriber. |
即使发送方应用程序仅发送一次消息,接收方应用程序也可能多次接收该消息。
Even when a sender application sends a message only once, the receiver application may receive the message more than once.
|
消息接收者如何处理重复的消息? How can a message receiver deal with duplicate messages? |
第 3 章“消息传递系统”中的通道模式讨论了如何通过使用保证传递来使消息传递通道可靠。然而,即使是一些可靠的消息传递实现也可能会产生重复的消息。在其他场景下,保证交付可能不可用,因为通信依赖于本质上不可靠的协议。许多 B2B(企业对企业)集成场景就是这种情况,其中消息必须使用 HTTP 通过 Internet 发送。在这些情况下,通常只能通过重新发送消息直到接收者返回确认来保证消息传递。但是,如果由于连接不可靠而导致确认丢失,发送方可能会重新发送接收方已收到的消息(见图)。
The channel patterns in Chapter 3, "Messaging Systems," discuss how to make messaging channels reliable by using Guaranteed Delivery. However, even some reliable messaging implementations can produce duplicate messages. In other scenarios, Guaranteed Delivery may not be available because the communication relies on inherently unreliable protocols. This is the case in many B2B (business-to-business) integration scenarios where messages have to be sent over the Internet using HTTP. In these cases, message delivery can generally only be guaranteed by resending the message until an acknowledgment is returned from the recipient. However, if the acknowledgment is lost due to an unreliable connection, the sender may resend a message that the receiver had already received (see figure).
由于发送确认时出现问题而导致消息重复
Message Duplication Because of Problem Sending Acknowledgment
许多消息传递系统都采用内置机制来消除重复消息,以便应用程序不必担心重复消息。然而,消除消息传递基础设施内的重复项会导致额外的开销。例如,如果接收器本质上对重复消息具有弹性,则在允许重复的情况下,可以增加处理查询式命令消息的消息传递吞吐量的无状态接收器。因此,某些消息传递系统仅提供至少一次传递,并让应用程序处理重复的消息。其他允许应用程序指定是否处理重复项(例如,JMS 规范定义了DUPS_OK_ACKNOWLEDGE模式)。
Many messaging systems incorporate built-in mechanisms to eliminate duplicate messages so that the application does not have to worry about duplicates. However, eliminating duplicates inside the messaging infrastructure causes additional overhead. If the receiver is inherently resilient against duplicate messagesfor example, a stateless receiver that processes query-style Command Messagesmessaging throughput can be increased if duplicates are allowed. For this reason, some messaging systems only provide at-least-once delivery and let the application deal with duplicate messages. Others allow the application to specify whether or not it deals with duplicates (for example, the JMS specification defines a DUPS_OK_ACKNOWLEDGE mode).
另一种可能产生重复消息的场景是失败的分布式事务。许多通过商业适配器连接到消息传递基础设施的打包应用程序无法正确参与分布式两阶段提交。当一条消息发送到多个应用程序并且该消息导致这些应用程序中的一个或多个失败时,可能很难从这种不一致状态中恢复。如果接收方被设计为忽略重复消息,则发送方只需将消息重新发送给所有接收方即可。那些已经接收并处理原始消息的收件人将简单地忽略重新发送的消息。那些无法正确使用原始消息的应用程序将应用重新发送的消息。
Another scenario that can produce duplicate messages is a failed distributed transaction. Many packaged applications that are connected to the messaging infrastructure through commercial adapters cannot properly participate in a distributed two-phase commit. When a message is sent to multiple applications and the message causes one or more of these applications to fail, it may be difficult to recover from this inconsistent state. If receivers are designed to ignore duplicate messages, the sender can simply resend the message to all recipients. Those recipients that had already received and processed the original message will simply ignore the resend. Those applications that were not able to properly consume the original message will apply the message that was resent.
|
将接收器设计为幂等接收器,即可以安全地多次接收同一消息的接收器。 Design a receiver to be an Idempotent Receiver, one that can safely receive the same message multiple times. |
幂等一词在数学中用于描述一个函数,如果将该函数应用于自身,则产生相同的结果:f ( x ) = f ( f ( x ) ) 。在消息传递中,这个概念转化为一条消息,无论接收一次还是多次,都具有相同的效果。这意味着即使接收者收到同一消息的重复项,也可以安全地重新发送消息,而不会引起任何问题。
The term idempotent is used in mathematics to describe a function that produces the same result if it is applied to itself: f (x) = f (f (x)). In Messaging this concepts translates into a message that has the same effect whether it is received once or multiple times. This means that a message can safely be resent without causing any problems even if the receiver receives duplicates of the same message.
幂等性可以通过两种主要方式实现:
Idempotency can be achieved through two primary means:
显式重复数据删除,即删除重复消息。
Explicit de-duping, which is the removal of duplicate messages.
定义消息语义以支持幂等性。
Defining the message semantics to support idempotency.
收件人可以通过跟踪已收到的消息来显式删除重复消息(假设 de-dupe 是一个正确的英语单词)。唯一的消息标识符简化了此任务,并有助于检测具有相同消息内容的两条合法消息到达的情况。通过使用单独的字段(消息标识符),我们不会将重复消息的语义与消息内容联系起来。然后,我们为每条消息分配一个唯一的消息标识符。许多消息传递系统(例如符合 JMS 的消息传递工具)会自动为每条消息分配唯一的消息标识符,而应用程序无需担心它们。
The recipient can explicitly de-dupe messages (let's assume de-dupe is a proper English word) by keeping track of messages that it already received. A unique message identifier simplifies this task and helps detect those cases where two legitimate messages with the same message content arrive. By using a separate field, the message identifier, we do not tie the semantics of a duplicate message to the message content. We then assign a unique message identifier to each message. Many messaging systems, such as JMS-compliant messaging tools, automatically assign unique message identifiers to each message without the applications having to worry about them.
为了基于消息标识符来检测和消除重复消息,消息接收者必须保留已接收到的消息标识符的列表。关键的设计决策之一是保留消息历史记录多长时间以及是否将历史记录保留到永久存储(例如磁盘)。该决定主要取决于发送者和接收者之间的合同。在最简单的情况下,发送者一次发送一条消息,在每条消息后等待接收者的确认。在这种情况下,接收方将任何传入消息的消息标识符与先前消息的标识符进行比较就足够了。如果标识符相同,它将忽略新消息。有效地,接收者保留单个消息的历史记录。在实践中,这种通信方式可能非常低效,特别是当延迟(消息从发送方传输到接收方的时间)相对于所需的消息吞吐量而言非常显着时。在这些情况下,发送者可能想要发送一整组消息而不等待每个消息的确认。但这意味着接收方必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于 这种通信方式的效率可能非常低,尤其是当延迟(消息从发送方传输到接收方的时间)相对于所需的消息吞吐量而言相当大时。在这些情况下,发送者可能想要发送一整组消息而不等待每个消息的确认。但这意味着接收方必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于 这种通信方式的效率可能非常低,特别是当延迟(消息从发送方传输到接收方的时间)相对于所需的消息吞吐量而言相当大时。在这些情况下,发送者可能想要发送一整组消息而不等待每个消息的确认。但这意味着接收方必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于 特别是如果延迟(消息从发送方传输到接收方的时间)相对于所需的消息吞吐量而言很重要。在这些情况下,发送者可能想要发送一整组消息而不等待每个消息的确认。但这意味着接收方必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于 特别是如果延迟(消息从发送方传输到接收方的时间)相对于所需的消息吞吐量而言很重要。在这些情况下,发送者可能想要发送一整组消息而不等待每个消息的确认。但这意味着接收方必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于 接收者必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于 接收者必须为已接收的消息保留更长的标识符历史记录。接收方“内存”的大小取决于发送方在没有得到接收方确认的情况下可以发送的消息数量。这个问题类似于重排序器。
In order to detect and eliminate duplicate messages based on the message identifier, the message recipient has to keep a list of already received message identifiers. One of the key design decisions is how long to keep this history of messages and whether to persist the history to permanent storage such as disk. This decision depends primarily on the contract between the sender and the receiver. In the simplest case, the sender sends one message at a time, awaiting the receiver's acknowledgment after every message. In this scenario, it is sufficient for the receiver to compare the message identifier of any incoming message to the identifier of the previous message. It will then ignore the new message if the identifiers are identical. Effectively, the receiver keeps a history of a single message. In practice, this style of communication can be very inefficient, especially if the latency (the time for the message to travel from the sender to the receiver) is significant relative to the desired message throughput. In these situations, the sender may want to send a whole set of messages without awaiting acknowledgment for each one. This implies, though, that the receiver has to keep a longer history of identifiers for already received messages. The size of the receiver's "memory" depends on the number of messages the sender can send without having gotten an acknowledgment from the receiver. This problem resembles the considerations presented in the Resequencer.
消除重复消息是另一个例子,通过仔细研究低级 TCP/IP 协议,我们可以学到很多东西。当 IP 网络数据包通过网络路由时,可能会生成重复的数据包。TCP/IP 协议通过为每个数据包附加唯一的标识符来确保消除重复的数据包。发送方和接收方协商接收方分配的“窗口大小”,以便检测重复项。有关 TCP/IP 如何实现此机制的全面讨论,请参阅 [ Stevens ]。
Eliminating duplicate messages is another example where we can learn quite a bit by having a closer look at the low-level TCP/IP protocol. When IP network packets are routed across the network, duplicate packets can be generated. The TCP/IP protocol ensures elimination of duplicate packets by attaching a unique identifier to each packet. Sender and receiver negotiate a "window size" that the recipient allocates in order to detect duplicates. For a thorough discussion of how TCP/IP implements this mechanism, see [Stevens].
在某些情况下,使用业务密钥作为消息标识符并让持久层处理重复数据删除可能很诱人。例如,假设应用程序将传入订单保存到数据库中。如果每个订单都包含唯一的订单号,并且我们将数据库配置为在订单号字段上使用唯一键,则如果收到重复的订单消息,则插入数据库的操作将失败。这个解决方案看起来很优雅,因为我们将重复检查委托给数据库系统,这在检测重复键方面非常有效。但我们必须谨慎,因为我们将双重语义与单个字段相关联。具体来说,我们将与基础设施相关的语义(重复消息)绑定到业务字段(订单号)。想象一下,业务需求发生变化,以便客户可以通过发送具有相同订单号的另一条消息来修改现有订单(这很常见)。我们现在必须更改消息结构,因为我们将唯一消息标识符绑定到业务字段。因此,最好避免使用双重语义重载单个字段。
In some cases, it may be tempting to use a business key as the message identifier and let the persistence layer handle the de-duping. For example, let's assume that an application persists incoming orders into a database. If each order contains a unique order number, and we configure the database to use a unique key on the order number field, the insert operation into the database would fail if a duplicate order message is received. This solution appears elegant because we delegated the checking of duplicates to the database systems, which is very efficient at detecting duplicate keys. But we have to be cautious because we associated dual semantics to a single field. Specifically, we tied infrastructure-related semantics (a duplicate message) to a business field (order number). Imagine that the business requirements change so that customers can amend existing orders by sending another message with the same order number (this is quite common). We would now have to make changes to our message structure, since we tied the unique message identifier to a business field. Therefore, it is best to avoid overloading a single field with dual semantics.
使用数据库强制重复数据删除有时是通过消息基础设施供应商提供的数据库适配器来完成的。在许多情况下,这些适配器无法消除重复项,因此必须将此功能委托给数据库。
Using a database to force de-duping is sometimes done with database adapters provided by the messaging infrastructure vendors. In many cases, these adapters are not capable of eliminating duplicates, so this function has to be delegated to the database.
实现幂等性的另一种方法是定义消息的语义,以便重新发送消息不会影响系统。例如,我们可以将消息更改为“将帐户 12345 的余额设置为 110 美元”,而不是将消息定义为“向帐户 12345 添加 10 美元”。如果当前帐户余额为 100 美元,这两条消息将获得相同的结果。第二条消息是幂等的,因为接收两次不会有任何效果。诚然,此示例忽略了并发情况,例如另一条消息“将帐户 12345 的余额设置为 150 美元”在原始消息和重复消息之间到达的情况。
An alternative approach to achieve idempotency is to define the semantics of a message such that resending the message does not impact the system. For example, rather than defining a message as "Add $10 to account 12345," we could change the message to "Set the balance of account 12345 to $110." Both messages achieve the same result if the current account balance is $100. The second message is idempotent because receiving it twice will not have any effect. Admittedly, this example ignores concurrency situationsfor example, the case where another message, "Set the balance of account 12345 to $150," arrives between the original and the duplicate message.
|
示例: 微软 IDL (MIDL) Example: Microsoft IDL (MIDL) Microsoft 接口定义语言 (MIDL) 支持幂等性概念作为远程调用语义的一部分。可以使用[idempot]属性将远程过程声明为幂等。MIDL 规范指出“ [幂等]属性指定操作不会修改状态信息并在每次执行时返回相同的结果。多次执行例程与执行一次具有相同的效果。” The Microsoft Interface Definition Language (MIDL) supports the concept of idempotency as part of the remote call semantics. A remote procedure can be declared as idempotent by using the [idempotent] attribute. The MIDL specification states that the "[idempotent] attribute specifies that an operation does not modify state information and returns the same results each time it is performed. Performing the routine more than once has the same effect as performing it once." 接口IFoo;
[
uuid(5767B67C-3F02-40ba-8B85-D8516F20A83B),
指针默认值(唯一)
]
接口IFoo
{
[幂等]
bool 获取客户名称
(
[in] int 客户ID,
[输出] 字符*名称
);
}
interface IFoo;
[
uuid(5767B67C-3F02-40ba-8B85-D8516F20A83B),
pointer_default(unique)
]
interface IFoo
{
[idempotent]
bool GetCustomerName
(
[in] int CustomerID,
[out] char *Name
);
}
|
一个应用程序有一项服务希望可供其他应用程序使用。
An application has a service that it would like to make available to other applications.
|
应用程序如何设计可通过各种消息传递技术和非消息传递技术调用的服务? How can an application design a service to be invoked both via various messaging technologies and via non-messaging techniques? |
应用程序可能不想选择服务(服务层[ EAA ]中的操作)是否可以同步或异步调用:它可能希望支持同一服务的两种方法。然而,技术似乎可以迫使人们做出选择。例如,使用 EJB 实现的应用程序可能需要使用会话 bean 来支持同步客户端,但需要使用 MDB 来支持消息传递客户端。[1]
An application may not want to choose whether a service (an operation in a Service Layer [EAA]) can be invoked synchronously or asynchronously: It may want to support both approaches for the same service. Yet, technologies can seem to force the choice. For example, an application implemented using EJB may need to use a session bean to support synchronous clients but an MDB to support messaging clients.[1]
[1]感谢 Mark Weitzel 提供的这个例子。
[1] Thanks to Mark Weitzel for this example.
设计与其他应用程序(例如 B2B 应用程序)一起使用的应用程序的开发人员可能不知道他们正在与哪些其他应用程序通信以及各种通信将如何工作。有太多不同的消息传递技术和数据格式,无法尝试支持每一种以防万一。[2]
Developers designing an application to work with other applications, such as a B2B application, may not know what other applications they're communicating with and how the various communications will work. There are too many different messaging technologies and data formats to try to support every one just in case it's needed.[2]
[2]感谢 Luke Hohmann 提供此示例。
[2] Thanks to Luke Hohmann for this example.
接收和处理消息涉及多个步骤;分开这些步骤可能很困难并且不必要地复杂。然而,将接收消息、提取消息内容以及作用于这些内容来执行工作的这些任务混合在一起的消息端点代码可能难以重用。
Receiving and processing a message involves a number of steps; separating these steps can be difficult and unnecessarily complex. Yet, Message Endpoint code that mixes together these tasksreceiving the message, extracting its contents, and acting on those contents to perform workcan be difficult to reuse.
当为多种通信风格设计客户端时,似乎有必要为每种风格重新实现服务。这使得支持每种新样式变得很麻烦,并产生每种样式可能不会产生完全相同的行为的风险。我们需要的是一种单一服务支持多种通信方式的方法。
When designing clients for multiple styles of communication, it may well seem necessary to reimplement the service for each style. This makes supporting each new style cumbersome and creates the risk that each style may not produce quite the same behavior. What is needed is a way for a single service to support multiple styles of communication.
|
设计一个服务激活器,将通道上的消息连接到正在访问的服务。 Design a Service Activator that connects the messages on the channel to the service being accessed. |
服务激活器可以是单向的(仅请求)或双向的(请求-回复) 。服务可以像方法调用一样简单,同步且非远程,或者是服务层[ EAA ] 的一部分。激活器可以被硬编码为始终调用相同的服务,或者它可以使用反射来调用消息指示的服务。激活器处理所有消息传递详细信息并像任何其他客户端一样调用服务,这样服务甚至不知道它是通过消息传递调用的。
A Service Activator can be one-way (request only) or two-way (Request-Reply ). The service can be as simple as a method callsynchronous and non-remoteperhaps part of a Service Layer [EAA]. The activator can be hard-coded to always invoke the same service, or it can use reflection to invoke the service indicated by the message. The activator handles all of the messaging details and invokes the service like any other client, such that the service doesn't even know it's being invoked through messaging.
请求-回复的服务激活器序列
Service Activator Sequence for Request-Reply
服务激活器处理接收请求消息(作为轮询消费者或事件驱动消费者) 。它知道消息的格式并处理消息以提取所需的信息,以了解要调用什么服务以及要传入什么参数值。然后,激活器像该服务的任何其他客户端一样调用该服务,并在服务执行时阻塞。当服务完成并返回一个值时,激活器可以选择创建一条包含该值的回复消息并将其返回给请求者。(回复使服务调用成为请求-回复消息传递的示例。)
The Service Activator handles receiving the request message (either as a Polling Consumer or as an Event-Driven Consumer ). It knows the message's format and processes the message to extract the information necessary to know what service to invoke and what parameter values to pass in. The activator then invokes the service just like any other client of the service and blocks while the service executes. When the service completes and returns a value, the activator can optionally create a reply message containing the value and return it to the requestor. (The reply makes the service invocation an example of Request-Reply messaging.)
服务激活器使服务能够被编写,就好像它总是被同步调用一样。激活器接收异步消息,确定要调用什么服务以及传递什么数据,然后同步调用该服务。该服务设计为无需消息传递即可工作,但激活器使其可以通过消息传递轻松调用。
A Service Activator enables a service to be written as though it's always going to be invoked synchronously. The activator receives the asynchronous message, determines what service to invoke and what data to pass it, and then invokes the service synchronously. The service is designed to work without messaging, yet the activator enables it to easily be invoked via messaging.
如果服务激活器无法成功处理该消息,则该消息无效,应将其移至无效消息通道。如果可以处理消息并且成功调用服务,则执行服务过程中发生的任何错误都是应用程序中的语义错误,应由应用程序处理。
If the Service Activator cannot process the message successfully, the message is invalid and should be moved to an Invalid Message Channel. If the message can be processed and the service is invoked successfully, then any errors that occur as part of executing the service are semantic errors in the application and should be handled by the application.
开发人员可能仍然无法预测合作伙伴可能希望访问其服务的每种方式,但他们至少知道他们的应用程序将提供哪些服务并且可以实现这些服务。然后,根据需要为不同的技术和格式实施新的激活器相对容易。
Developers still may not be able to predict every way partners might wish to access their services, but they do at least know what services their application will provide and can implement those. Then, implementing new activators for different technologies and formats as needed is relatively easy.
此服务激活器模式也记录在 [ CoreJ2EE] 中,这是该模式最初命名的地方。该版本的模式与此版本有些不同,它假设激活器是事件驱动的消费者,并且服务已经存在,因此可以将激活器添加到服务中,但是两个版本都以非常相似的方式针对同一问题提出了相同的解决方案时尚。服务激活器与半同步/半异步模式[ POSA2]相关,该模式将服务处理分为同步层和异步层。
This Service Activator pattern is also documented in [CoreJ2EE], which is where the pattern was originally named. That version of the pattern is somewhat different from this oneit assumes the activator is an Event-Driven Consumer and that the service already exists so that the activator can be added to the servicebut both versions propose the same solution to the same problem in a very similar fashion. Service Activator is related to the Half-Sync/Half-Async pattern [POSA2], which separates service processing into synchronous and asynchronous layers.
服务激活器通常接收命令消息,其中描述了要调用的服务。服务激活器充当消息传递网关,将消息传递详细信息与服务分开。激活器可以是轮询消费者或事件驱动消费者。如果服务是事务性的,则激活器应该是事务性客户端,以便消息消费可以参与与服务调用相同的事务。多个激活器可以是竞争消费者或由消息。如果一个Service Activator无法成功处理消息,它应该将消息发送到Invalid Message Channel 。
A Service Activator usually receives Command Messages, which describe what service to invoke. A Service Activator serves as a Messaging Gateway, separating the messaging details from the service. The activator can be a Polling Consumer or an Event-Driven Consumer. If the service is transactional, the activator should be a Transactional Client so that the message consumption can participate in the same transaction as the service invocation. Multiple activators can be Competing Consumers or coordinated by a Message Dispatcher. If a Service Activator cannot process a message successfully, it should send the message to an Invalid Message Channel.
|
示例: J2EE Enterprise JavaBean Example: J2EE Enterprise JavaBeans 例如,考虑 J2EE 中的 EJB [ EJB 2.0 ]:将服务封装为会话 Bean,然后为各种消息传递场景实现 MDB:一个用于使用一种格式的消息的 JMS 目标;另一个用于使用一种格式的消息的 MDB。另一个使用另一种格式用于不同的目的地;另一个用于 Web 服务/SOAP 消息;等等。每个通过调用服务来处理消息的 MDB 都是一个Service Activator 。希望同步调用服务的客户端可以直接访问会话 bean。 Consider, for example, EJBs [EJB 2.0] in J2EE: Encapsulate the service as a session bean, and then implement MDBs for various messaging scenarios: one for a JMS destination using messages of one format; another for a different destination using another format; another for a Web service/SOAP message; and so on. Each MDB that processes the message by invoking the service is a Service Activator. Clients that wish to invoke the service synchronously can access the session bean directly. |
虽然开发消息传递解决方案并非易事,但在生产中操作此类解决方案同样具有挑战性:基于消息的集成解决方案可能会在一天内生成、路由和转换数千甚至数百万条消息。我们必须处理参与系统中的异常、性能瓶颈和变化。为了使事情变得更具挑战性,组件分布在许多可以驻留在多个位置的平台和机器上。
While developing a messaging solution is no easy task, operating such a solution in production is equally challenging: A message-based integration solution may produce, route, and transform thousands or even millions of messages in a day. We have to deal with exceptions, performance bottlenecks, and changes in the participating systems. To make things ever more challenging, components are distributed across many platforms and machines that can reside at multiple locations.
除了集成分布式打包和定制应用程序固有的复杂性和规模之外,松散耦合的架构优势实际上使测试和调试系统变得更加困难。Martin Fowler 将此称为“架构师的梦想,开发人员的噩梦”症状:松散耦合和间接的架构原则减少了系统彼此之间的假设,从而提供了灵活性。然而,测试消息生产者不知道消息消费者是谁的系统可能具有挑战性。再加上消息传递的异步和时间方面,事情会变得更加复杂。例如,消息传递解决方案甚至可能不是为消息生产者设计的以从接收者接收回复消息。同样,消息传递基础设施通常保证消息的传递,但不保证传递时间。这使得开发依赖于消息传递结果的测试用例变得困难。
Besides the inherent complexities and scale of integrating distributed packaged and custom applications, the architectural benefits of loose coupling actually make testing and debugging a system harder. Martin Fowler refers to this as the "architect's dream, developer's nightmare" symptom: Architectural principles of loose coupling and indirection reduce the assumptions systems make about each other and therefore provide flexibility. However, testing a system where a message producer is not aware of who the consumers of a message are can be challenging. Add to that the asynchronous and temporal aspects of messaging and things get even more complicated. For example, the messaging solution may not even be designed for the message producer to receive a reply message from the recipient(s). Likewise, the messaging infrastructure typically guarantees the delivery of the message but not the delivery time. This makes it hard to develop test cases that rely on the results of the message delivery.
在监控消息解决方案时,我们可以在两个不同的抽象级别跟踪消息流。典型的系统管理解决方案监视正在发送的消息数量或处理消息所需的时间。这些监控解决方案不会检查消息数据,除了消息头中的某些字段(例如消息标识符或消息历史记录) 。相比之下,业务活动监控(BAM) 解决方案重点关注消息中包含的有效负载数据,例如过去一小时内所有订单的美元价值。本节中介绍的许多模式都足够通用,可以用于任一目的。然而,由于 BAM 本身是一个全新的领域,并且与数据仓库有许多复杂性(我们根本没有触及过),因此我们决定在系统管理的背景下讨论这些模式。
When monitoring a message solution, we can track the flow of messages at two different levels of abstraction. A typical system management solution monitors how many messages are being sent or how long it took a message to be processed. These monitoring solutions do not inspect the message data except maybe for some fields in the message header such as the message identifier or the Message History. In contrast, business activity monitoring (BAM) solutions focus on the payload data contained in the message, for example, the dollar value of all orders placed in the last hour. Many of the patterns presented in this section are general enough that they can be used for either purpose. However, because BAM is a whole new field in itself and shares many complexities with data warehousing (something we have not touched on at all), we decided to discuss the patterns in the context of system management.
系统管理模式旨在满足这些需求,并提供工具来保持复杂的基于消息的系统运行。本章中的模式分为三类:监视和控制、观察和分析消息流量以及测试和调试。
System management patterns are designed to address these requirements and provide the tools to keep a complex message-based system running. The patterns in this chapter fall into three categories: monitoring and controlling, observing and analyzing message traffic, and testing and debugging.
控制总线提供单点控制来管理和监控分布式解决方案。它将多个组件连接到一个中央管理控制台,该控制台可以显示每个组件的状态并监视通过组件的消息流量。控制台还可用于向组件发送控制命令,例如更改消息流。
A Control Bus provides a single point of control to manage and monitor a distributed solution. It connects multiple components to a central management console that can display the status of each component and monitor message traffic through the components. The console can also be used to send control commands to components, for instance, to change the message flow.
我们可能希望通过其他步骤来路由消息,例如验证或日志记录。由于这些步骤会带来性能开销,因此我们可能希望能够通过控制总线打开和关闭它们。Detour给了我们这种能力。
We may want to route messages through additional steps, such as validation or logging. Because these steps can introduce performance overheads, we may want to be able to switch them on and off via the control bus. A Detour gives us this ability.
有时,我们希望在不影响主消息流的情况下检查消息的内容。有线窃听使我们能够窃听消息流量。
Sometimes, we want to inspect the contents of a message without affecting the primary message flow. A Wire Tap allows us to tap into message traffic.
当我们调试基于消息的系统时,了解特定消息的位置非常有帮助。消息历史记录保留消息访问过的所有组件的日志,而不会引入组件之间的依赖关系。
When we debug a message-based system, it is a great aid to know where a specific message has been. The Message History keeps a log of all components the message has visited without introducing dependencies between components.
虽然消息历史记录与单个消息相关联,但中央消息存储可以提供通过系统的每条消息的完整帐户。与消息历史记录相结合,消息存储可以分析消息可以通过系统的所有可能路径。
While the Message History is tied to an individual message, a central Message Store can provide a complete account of every message that traveled through the system. Combined with the Message History, the Message Store can analyze all possible paths messages can take through the system.
Wire Tap、Message History和Message Store帮助我们分析消息的异步流。为了跟踪发送到请求-答复服务的消息,我们需要在消息流中插入智能代理。
The Wire Tap, Message History, and Message Store help us analyze the asynchronous flow of a message. In order to track messages sent to request-reply services, we need to insert a Smart Proxy into the message stream.
在将消息系统部署到生产环境之前对其进行测试是一个非常好的主意。但测试不应该就此停止。您应该积极验证正在运行的消息系统是否继续正常运行。您可以通过定期向系统注入测试消息并验证结果来做到这一点。
Testing a messaging system before deploying it into production is a very good idea. But testing should not stop there. You should be actively verifying that the running messaging system continues to function properly. You can do this by periodically injecting a Test Message into the system and verifying the results.
当组件出现故障或行为异常时,很容易在通道上出现不需要的消息。在测试过程中,从通道中删除所有剩余消息非常有用,这样测试中的组件就不会收到“剩余”消息。Channel Purger可以帮我们做到这一点。
When a component fails or misbehaves, it is easy to end up with unwanted messages on a channel. During testing it is very useful to remove all remaining messages from a channel so that the components under test do not receive "leftover" messages. A Channel Purger does that for us.
当然,企业集成系统是分布式的。事实上,企业消息传递系统的定义品质之一是支持不同系统之间的通信。消息传递系统允许路由和转换信息,以便可以在这些系统之间交换数据。在大多数情况下,这些应用程序分布在多个网络、建筑物、城市或大陆。
Naturally, enterprise integration systems are distributed. In fact, one of the defining qualities of an enterprise messaging system is to enable communication between disparate systems. Messaging systems allow information to be routed and transformed so that data can be exchanged between these systems. In most cases, these applications are spread across multiple networks, buildings, cities, or continents.
|
我们如何有效地管理分布在多个平台和广泛地理区域的消息系统? How can we effectively administer a messaging system that is distributed across multiple platforms and a wide geographic area? |
分布式、松散耦合的架构具有灵活性和可扩展性。同时,也给该系统的管理和控制带来了严峻的挑战。例如,如何判断所有组件是否都已启动并正在运行?简单的进程状态是不够的,因为进程分布在许多机器上。另外,如果无法从远程计算机获取状态,是否意味着远程计算机无法正常工作,或者与远程计算机的通信可能受到干扰?
A distributed, loosely coupled architecture allows for flexibility and scalability. At the same time, it poses serious challenges for administration and control of such a system. For example, how can you tell whether all components are up and running? A simple process status won't suffice, because processes are distributed across many machines. Also, if you cannot obtain status from a remote machine, does it mean that the remote machine is not functioning, or might the communication with the remote machine be disturbed?
除了了解系统或组件是否启动并运行之外,您还需要监视系统的动态行为。消息吞吐量是多少?是否有任何异常延误?渠道满了吗?其中一些信息需要跟踪组件之间或通过组件的消息传播时间。这需要从不止一台机器收集和组合信息。
Besides just knowing whether a system or a component is up and running, you also need to monitor the dynamic behavior of the system. What is the message throughput? Are there any unusual delays? Are channels filling up? Some of this information requires tracking of message travel times between components or through components. This requires the collection and combination of information from more than one machine.
此外,仅从组件读取信息可能还不够。通常,您需要在系统运行时进行调整或更改配置设置。例如,您可能需要在系统运行时打开或关闭日志记录功能。许多应用程序使用属性文件和错误日志来读取配置信息并报告错误情况。只要应用程序由一台机器或可能由少量机器组成,这种方法往往效果很好。在大型分布式解决方案中,必须使用某种文件传输机制将属性文件复制到远程计算机,这要求每台计算机上的文件系统都可以远程访问。这可能会带来安全风险,并且如果计算机通过可能不支持文件映射协议的互联网或广域网连接,则可能会带来挑战。此外,必须仔细管理本地属性文件的版本,这将是一场管理噩梦。
Also, just reading information from components may not be sufficient. Often, you need to make adjustments or change configuration settings while the system is running. For example, you may need to turn logging features on or off while the system is running. Many applications use property files and error logs to read configuration information and report error conditions. This approach tends to work well as long as the application consists of a single machine or possibly a small number of machines. In a large, distributed solution, property files would have to be copied to remote machines using some file transfer mechanism, which requires the file system on every machine to be accessible remotely. This can pose security risks and can be challenging if the machines are connected over the Internet or a wide-area network that may not support file mapping protocols. Also, the versions of the local property files would have to be managed carefullya management nightmare waiting to happen.
尝试利用消息传递基础设施来执行其中一些任务似乎很自然。例如,我们可以向组件发送消息以更改其配置。该控制消息可以像常规消息一样被传输和路由。这将解决大部分沟通问题,但也带来了新的挑战。配置消息应遵守比常规应用程序消息更严格的安全策略。例如,一条格式错误的控制消息很容易导致组件瘫痪。另外,如果由于组件出现故障而导致消息在消息通道上排队怎么办?如果我们发送控制消息来重置组件,该控制消息将与所有其他消息一起排队,并且不会到达遇险的组件。一些消息系统支持消息优先级,这可以帮助将控制消息移动到队列的前面。然而,并非所有系统都提供这种能力,如果队列已满并拒绝接受另一条消息,则优先级可能无济于事。同样,一些控制消息的优先级低于应用消息。如果我们让组件定期发布状态消息,那么延迟或丢失“我还活着”控制消息可能比延迟或丢失“100 万美元的订单”消息要麻烦得多。并非所有系统都提供这种能力,如果队列已满并拒绝接受另一条消息,则优先级可能无济于事。同样,一些控制消息的优先级低于应用消息。如果我们让组件定期发布状态消息,那么延迟或丢失“我还活着”控制消息可能比延迟或丢失“100 万美元的订单”消息要麻烦得多。并非所有系统都提供这种能力,如果队列已满并拒绝接受另一条消息,则优先级可能无济于事。同样,一些控制消息的优先级低于应用消息。如果我们让组件定期发布状态消息,那么延迟或丢失“我还活着”控制消息可能比延迟或丢失“100 万美元的订单”消息要麻烦得多。
It seems natural to try to leverage the messaging infrastructure to perform some of these tasks. For example, we could send a message to a component to change its configuration. This control message could be transported and routed just like a regular message. This would solve most of the communication problems but also poses new challenges. Configuration messages should be subject to stricter security policies than are regular application messages. For example, one wrongly formatted control message could easily bring a component down. Also, what if messages are queued up on a message channel because a component is malfunctioning? If we send a control message to reset the component, this control message would get queued up with all the other messages and not reach the component in distress. Some messaging systems support message priorities that can help move control messages to the front of the queue. However, not all systems provide this ability, and the priority may not help if a queue is filled to the limit and refuses to accept another message. Likewise, some control messages are of a lower priority than application messages. If we have components publish periodic status messages, delaying or losing an "I am alive" control message may be a lot less troublesome than delaying or losing the "Order for $1 million" message.
|
使用控制总线来管理企业集成系统。控制总线使用与应用程序数据所使用的相同的消息传递机制,但使用单独的通道来传输与消息流中涉及的组件的管理相关的数据。 Use a Control Bus to manage an enterprise integration system. The Control Bus uses the same messaging mechanism used by the application data but uses separate channels to transmit data that is relevant to the management of components involved in the message flow. |
系统中的每个组件现在都连接到两个消息传递子系统:
Each component in the system is now connected to two messaging subsystems:
应用程序消息流
The Application Message Flow
控制总线
The Control Bus
应用程序消息流传输所有与应用程序相关的消息。组件订阅并发布到这些通道,就像在非托管场景中一样。此外,每个组件还从组成控制总线的通道发送和接收消息。这些通道连接到中央管理组件。
The application message flow transports all application-related messages. The components subscribe and publish to these channels just as they would in an unmanaged scenario. In addition, each component also sends and receives messages from the channels that make up the Control Bus. These channels connect to a central management component.
控制总线非常适合承载以下类型的消息:
The Control Bus is well suited to carry the following types of messages:
配置 消息流中涉及的每个 组件应该具有可根据需要进行更改的可配置参数。这些参数包括通道地址、消息数据格式、超时等。组件使用控制总线而不是属性文件从中央存储库检索此信息,从而允许在运行时进行集中配置和重新配置集成解决方案。例如,基于内容的路由器内的路由表可能需要根据系统条件(例如过载或组件故障)动态更新。
Configuration Each component involved in the message flow should have configurable parameters that can be changed as required. These parameters include channel addresses, message data formats, timeouts, and so on. Components use the Control Bus rather than property files to retrieve this information from a central repository, allowing a central point of configuration and the reconfiguration of the integration solution at runtime. For example, the routing table inside a Content-Based Router may need to be updated dynamically based on system conditions, such as overload or component failure.
心跳 每个组件可以按指定的上发送定期心跳消息,以便中央控制台应用程序可以验证该组件是否正常运行。此心跳还可能包括有关组件的度量,例如处理的消息数和机器上的可用内存量。
Heartbeat Each component may send a periodic heartbeat message on the Control Bus at specified intervals so that a central console application can verify that the component is functioning properly. This heartbeat may also include metrics about the component, such as number of messages processed and the amount of available memory on the machine.
测试消息 心跳消息告诉控制总线组件仍然处于活动状态,但它们可能提供有关组件正确处理消息的能力的有限信息。除了让组件定期向控制总线发布心跳消息之外,我们还可以将测试消息注入到将由组件处理的消息流中。我们稍后提取消息以查看组件是否正确处理消息。由于这种方法模糊了控制总线和应用程序消息流的定义,我们为其定义了一个单独的模式(请参阅测试消息)。
Test Messages Heartbeat messages tell the Control Bus that a component is still alive, but they may provide limited information on ability of the component to correctly process messages. In addition to having components publish periodic heartbeat messages to the Control Bus, we can inject test messages into the message stream that will be processed by the components. We extract the message later to see whether the component processed the message correctly. As this approach blurs the definition of the Control Bus and the application message flow, we defined a separate pattern for it (see Test Message).
异常 每个 组件都可以将异常条件传送到控制总线进行评估。严重的异常可能会导致操作员收到警报。定义异常处理的规则应在中央处理程序中指定。
Exceptions Each component can channel exception conditions to the Control Bus to be evaluated. Severe exceptions may cause an operator to be alerted. The rules to define exception handling should be specified in a central handler.
统计信息 每个 组件都可以收集有关处理的消息数量、平均吞吐量、处理消息的平均时间等的统计信息。其中一些数据可能会按消息类型进行拆分,因此我们可以确定某种类型的消息是否正在淹没系统。由于该消息的优先级往往低于其他消息,因此控制总线很可能对此类数据使用无保证或优先级较低的通道。
Statistics Each component can collect statistics about the number of messages processed, average throughput, average time to process a message, and so on. Some of this data may be split out by message type, so we can determine whether messages of a certain type are flooding the system. Since this message tends to be lower priority than other messages, it is likely that the Control Bus uses nonguaranteed or lower-priority channels for this type of data.
实时控制台 这里提到的 大多数功能都可以聚合在中央控制台中显示。从这里,操作员可以评估消息传递系统的运行状况,并在需要时采取纠正措施。
Live Console Most of the functions mentioned here can be aggregated for display in a central console. From here, operators can assess the health of the messaging system and take corrective action if needed.
控制总线支持的许多功能类似于用于监视和维护任何网络解决方案的传统网络管理功能。控制总线允许我们在消息传递系统级别实现等效的管理功能,从本质上将它们从低级 IP 网络级别提升到更丰富的消息传递级别。提供管理功能对于消息传递基础设施的成功运行与网络基础设施的成功运行一样重要。不幸的是,由于缺乏消息传递解决方案的管理标准,因此很难为消息传递系统构建企业范围内的、可重用的管理解决方案。
Many of the functions that a Control Bus supports resemble traditional network management functions that are used to monitor and maintain any networked solution. A Control Bus allows us to implement equivalent management functions at the messaging system levelessentially elevating them from the low-level IP network level to the richer messaging level. Providing management functionality is as vital to the successful operation of a messaging infrastructure as it is for a network infrastructure. Unfortunately, the absence of management standards for messaging solutions makes it difficult to build enterprisewide, reusable management solutions for messaging systems.
当我们设计消息处理组件时,我们围绕三个接口构建核心处理器(见图)。入站数据接口接收来自消息通道的传入消息。出站数据接口将处理后的消息发送到出站通道。控制接口向控制总线发送控制消息以及从控制总线接收控制消息。
When we design message processing components, we architect the core processor around three interfaces (see figure). The inbound data interface receives incoming messages from the message channel. The outbound data interface sends processed messages to the outbound channel. The control interface sends and receives control messages from and to the Control Bus.
消息传递组件的关键接口
Key Interfaces of a Messaging Component
|
示例: 贷款经纪人示例 Example: Instrumenting the Loan Broker Example 在第 12 章“插曲:系统管理示例”中,我们展示了如何使用控制总线来检测第 9 章“插曲:组合消息传递”中的贷款经纪人示例。该工具包括一个简单的管理控制台,可以实时显示组件的状态(请参阅第 12 章中的“贷款经纪人系统管理”) 。 In Chapter 12, "Interlude: Systems Management Example," we show how to use a Control Bus to instrument the loan broker example from Chapter 9, "Interlude: Composed Messaging." The instrumentation includes a simple management console that displays the status of components in real time (see "Loan Broker System Management" in Chapter 12). |
有时我们想根据外部因素修改消息的路由。
Sometimes we want to modify the route that messages take based on external factors.
|
如何通过中间步骤路由消息以执行验证、测试或调试功能? How can you route a message through intermediate steps to perform validation, testing, or debugging functions? |
对组件之间传输的消息执行验证可能是一个非常有用的调试工具。然而,这些额外的步骤可能并不总是需要的,并且如果总是执行它们会减慢系统的速度。
Performing validations on messages that travel between components can be a very useful debugging tool. However, these extra steps may not always be required and would slow down the system if they are always executed.
能够基于集中设置包含或跳过这些步骤可能是一种非常有效的调试或性能调整工具。例如,当我们测试系统时,我们可能希望通过额外的验证步骤传递消息。在生产过程中绕过这些步骤可能会提高性能。我们可以将这些验证与源代码中的断言语句进行比较,这些语句在调试配置中执行,但不在可执行文件的发布配置中执行。
Being able to include or skip these steps based on a central setting can be a very effective debugging or performance tuning tool. For example, while we test a system, we may want to pass messages through additional validation steps. Bypassing these steps during production may improve performance. We can compare these validations to assert statements in source code that are executed in the debug configuration but not in the release configuration of the executable.
同样,在故障排除期间,通过附加步骤路由消息以用于日志记录或监视目的可能很有用。能够打开和关闭这些日志记录步骤使我们能够在正常情况下最大化消息吞吐量。
Likewise, during troubleshooting, it may be useful to route messages through additional steps for logging or monitoring purposes. Being able to turn these logging steps on and off allows us to maximize message throughput under normal circumstances.
|
使用通过控制总线控制的基于上下文的路由器构造Detour 。在一种状态下,路由器通过附加步骤路由传入消息,而在另一种状态下,路由器将消息直接路由到目标通道。 Construct a Detour with a Context-Based Router controlled via the Control Bus. In one state, the router routes incoming messages through additional steps, while in the other it routes messages directly to the destination channel. |
Detour使用一个简单的基于上下文的路由器,具有两个输出通道。一个输出通道将未修改的消息传递到原始目的地。当收到控制总线的指示时,Detour将消息路由到不同的通道。该通道将消息发送到可以检查和/或修改消息的其他组件。最终,这些组件将消息路由到同一目的地。
The Detour uses a simple context-based router with two output channels. One output channel passes the unmodified message to the original destination. When instructed by the Control Bus, the Detour routes messages to a different channel. This channel sends the message to additional components that can inspect and/or modify the message. Ultimately, these components route the message to the same destination.
如果绕行路线仅包含单个组件,则将绕行开关和组件组合到单个过滤器中可能会更有效。然而,该解决方案假设可以修改绕行路径中的组件以包括通过控制总线控制的旁路逻辑。
If the detour route contains only a single component, it may be more efficient to combine the Detour switch and the component into a single filter. However, this solution assumes that the component in the detour path can be modified to include the bypass logic controlled via the Control Bus.
通过控制总线控制Detour的优势在于,可以使用从控制台到所有Detour 的发布-订阅通道,通过控制总线上的单个命令同时激活或停用多个Detour。
The strength of controlling the Detour over the Control Bus is that multiple Detours can be activated or deactivated simultaneously with a single command on the Control Bus using a Publish-Subscribe Channel from the control console to all Detours.
点对点通道通常用于文档消息,因为但是,对于测试、监控或故障排除,能够检查跨通道传输的所有消息可能会很有用。
Point-to-Point Channels are often used for Document Messages because they ensure that exactly one consumer will consume each message. However, for testing, monitoring, or troubleshooting, it may be useful to be able to inspect all messages that travel across the channel.
|
如何检查在点对点通道上传输的消息? How do you inspect messages that travel on a Point-to-Point Channel? |
查看哪些消息遍历通道非常有用,例如,出于简单的调试目的或将消息存储在消息存储中。您不能只是向点对点通道添加另一个侦听器,因为它会消耗通道外的消息并阻止预期接收者使用该消息。
It can be very useful to see which messages traverse a channelfor example, for simple debugging purposes or to store messages in a Message Store. You can't just add another listener to the Point-to-Point Channel, because it would consume messages off the channel and prevent the intended recipient from being able to consume the message.
或者,您可以让发送者或接收者负责将消息发布到单独的通道以供检查。然而,这将迫使我们修改一组可能很大的组件。此外,如果我们处理打包的应用程序,我们甚至可能无法修改该应用程序。
Alternatively, you could make the sender or the receiver responsible to publish the message to a separate channel for inspection. However, this would force us to modify a potentially large set of components. Additionally, if we are dealing with packaged applications, we may not even be able to modify the application.
您还可以考虑将频道更改为Publish-Subscribe Channel 。这将允许其他侦听器检查消息,而不会干扰消息流。然而,发布-订阅通道改变了通道的语义。例如,多个竞争消费者可能会从通道中消费消息,因为只有一个消费者可以接收特定消息。将频道更改为发布-订阅频道会让每个消费者收到每条消息。例如,如果传入消息代表现在被多次处理的订单,这可能是非常不希望的。即使只有一个消费者在通道上侦听,使用发布-订阅通道也可能比使用点对点通道效率低或可靠性低。
You could also consider changing the channel to a Publish-Subscribe Channel. This would allow additional listeners to inspect messages without disturbing the flow of messages. However, a Publish-Subscribe Channel changes the semantics of the channel. For example, multiple Competing Consumers may be consuming messages off the channel, relying on the fact that only one consumer can receive a specific message. Changing the channel to a Publish-Subscribe Channel would cause each consumer to receive each message. This could be very undesirable, for example, if the incoming messages represent orders that now get processed multiple times. Even if only a single consumer listens on the channel, using a Publish-Subscribe Channel may be less efficient or less reliable than using a Point-to-Point Channel.
许多消息传递系统提供了一种 peek 方法,允许组件检查点对点通道内的消息而不消耗该消息。不过,这种方法有一个重要的限制:一旦目标消费者消费了消息,peek 方法就无法再看到该消息。因此,这种方法不允许我们在消息被消费后对其进行分析。
Many messaging systems provide a peek method that allows a component to inspect messages inside a Point-to-Point Channel without consuming the message. This approach has one important limitation though: Once the intended consumer consumes the message, the peek method can no longer see the message. Therefore, this approach does not allow us to analyze messages after they have been consumed.
您可以将一个组件插入到通道中(“拦截器”的一种形式)来执行任何必要的检查。该组件将使用传入通道中的消息,检查该消息,并将未修改的消息传递到输出通道。但是,检查类型经常依赖于来自多个通道的消息(例如,测量消息运行时间),因此这一功能不能通过单个通道内的单个过滤器来实现。
You could insert a component into the channel (a form of "interceptor') that performs any necessary inspection. The component would consume a message off the incoming channel, inspect the message, and pass the unmodified message to the output channel. However, the type of inspection frequently depends on messages from more than one channel (e.g., to measure message runtime), so this function cannot be implemented by a single filter inside a single channel.
|
将Wire Tap插入通道,这是一个简单的收件人列表,可将每条传入消息发布到主通道以及辅助通道。 Insert a Wire Tap into the channel, a simple Recipient List that publishes each incoming message to the main channel as well as to a secondary channel. |
Wire Tap(也称为 tee)是具有两个输出通道的固定收件人列表。它消耗输入通道上的消息并将未修改的消息发布到两个输出通道。要将Wire Tap插入通道,您需要创建一个附加通道并更改目标接收器以使用第二个通道。由于分析是由单独的组件执行的,因此我们可以将通用的Wire Tap插入任何通道,而不会产生无意中修改主通道行为的危险。这可以提高重用性并降低在检测现有解决方案时出现不良副作用的风险。
The Wire Tap (also known as tee) is a fixed Recipient List with two output channels. It consumes messages off the input channel and publishes the unmodified message to both output channels. To insert the Wire Tap into a channel, you need to create an additional channel and change the destination receiver to consume the second channel. Because the analysis is performed by a separate component, we can insert a generic Wire Tap into any channel without any danger of unintentionally modifying the primary channel behavior. This improves reuse and reduces the risk of undesirable side effects when instrumenting an existing solution.
通过控制总线对Wire Tap进行编程可能会很有用,以便可以打开或关闭辅助通道(“tap”)。这样,可以指示Wire Tap仅在测试或调试周期期间将消息发布到辅助通道。
It might be useful to make the Wire Tap programmable over the Control Bus so that the secondary channel (the "tap") can be turned on or off. This way, the Wire Tap can be instructed to publish messages to the secondary channel only during testing or debugging cycles.
Wire Tap的主要缺点是消费和重新发布消息会产生额外的延迟。许多集成工具套件会自动解码消息,即使该消息未经修改就发布到另一个渠道。此外,新消息将收到与原始消息不同的新消息 ID 和新时间戳。这些操作可能会增加额外的开销并导致现有机制崩溃。例如,如果原始消息流使用原始消息的消息ID作为关联标识符,由于重新发布的消息的消息 ID 与原始消息的消息 ID 不同,解决方案将崩溃。这是使用消息 ID 作为关联标识符通常不是一个好主意的原因之一。
The main disadvantage of the Wire Tap is the additional latency incurred by consuming and republishing a message. Many integration tool suites automatically decode a message even if it is published to another channel without modification. Also, the new message will receive a new message ID and new timestamps that are different from the original message. These operations can add up to additional overhead and cause existing mechanisms to break. For example, if the original message flow uses the message ID of the original message as a Correlation Identifier, the solution will break because the message ID of the republished message is different from the message ID of the original message. This is one of the reasons that it is generally not a good idea to use the message ID as a Correlation Identifier.
由于Wire Tap发布两条单独的消息,因此重要的是不要通过消息 ID 来关联这些消息。即使主通道和辅助通道接收相同的消息,大多数消息传递系统也会自动为系统中的每条消息分配一个新的消息 ID。这意味着原始消息和“重复”消息具有不同的消息 ID。
Because the Wire Tap publishes two separate messages, it is important not to correlate between these messages by their message ID. Even though the primary and the secondary channel receive identical messages, most messaging systems automatically assign a new message ID to each message in the system. This means that the original message and the "duplicate" message have different message IDs.
现有的Message Broker可以轻松地增强为Wire Tap,因为所有消息都已通过此中央组件。
An existing Message Broker can easily be augmented to act as a Wire Tap because all messages already pass through this central component.
Wire Tap的一个重要限制是它无法改变流经通道的消息。如果您需要能够操纵消息,请使用Detour。
An important limitation of the Wire Tap is that it cannot alter the messages flowing across the channel. If you need to be able to manipulate messages, use a Detour instead.
|
示例: 贷款经纪人 Example: Loan Broker 在第 12 章“插曲:系统管理示例”中,我们增强了贷款经纪人示例,在信用局的请求通道上包含 Wire Tap,以记录对此外部服务发出的所有请求(请参阅“贷款经纪人系统管理”) ”,第 12 章)。 In Chapter 12, "Interlude: Systems Management Example," we enhance the loan broker example to include a Wire Tap on the request channel to the credit bureau to keep a log of all requests made to this external service (see "Loan Broker System Management" in Chapter 12). |
|
示例: 使用多个Wire Tap来测量消息运行时间 Example: Using Multiple Wire Taps to Measure Message Runtime Wire Tap的优点之一是我们可以组合多个Wire Tap将消息副本发送到中央组件进行分析。该组件可以是消息存储或其他分析消息之间关系的组件,例如两个相关消息之间的时间间隔(见图)。在分析消息运行时间时,我们应该根据次要消息的发送时间进行计算,这样计算就不会因次要消息的传播时间而产生偏差。 One of the strengths of the Wire Tap is that we can combine multiple Wire Taps to send copies of messages to a central component for analysis. That component can be a Message Store or another component that analyzes relationships between messages, such as the time interval between two related messages (see figure). When analyzing the message runtime, we should base the computation on the time when the secondary messages were sent so that the computation is not skewed by the travel time of the secondary messages. 使用一对丝锥来分析消息运行时间 Using a Pair of Wire Taps to Analyze Message Runtime |
基于消息的系统的主要优点之一是参与者之间的松散耦合。消息发送者和接收者对彼此的身份没有(或很少)做出任何假设。如果消息接收者从消息通道检索消息,它通常不知道也不关心哪个应用程序将消息放入通道上。根据定义,该消息是独立的,并且不与特定的发送者关联。这是基于消息的系统的架构优势之一。
One of key benefits of a message-based system is the loose coupling between participants; the message sender and recipient make no (or few) assumptions about each other's identity. If a message recipient retrieves a message from a message channel, it generally does not know nor care which application put the message on the channel. The message is by definition self-contained and is not associated with a specific sender. This is one of the architectural strengths of message-based systems.
然而,相同的属性可能会使调试和分析依赖关系变得非常困难。如果我们不确定消息的去向,我们如何评估消息格式更改的影响?同样,如果我们不知道哪个应用程序发布了特定消息,则很难纠正该消息的问题。
However, the same property can make debugging and analyzing dependencies very difficult. If we are not sure where a message goes, how can we assess the impact of a change in the message format? Likewise, if we don't know which application published a particular message, it is difficult to correct a problem with the message.
|
如何有效地分析和调试松耦合系统中的消息流? How can we effectively analyze and debug the flow of messages in a loosely coupled system? |
控制总线监视处理消息的每个组件的状态,但它不关心单个消息所采用的路由。您可以修改每个组件,以发布通过它传递到控制总线的每条消息的唯一消息标识符。然后可以将这些信息收集到一个公共数据库(消息存储)中。这种方法需要大量的基础设施,包括单独的数据存储。此外,如果组件需要检查消息的历史记录,则必须对中央数据库执行查询,从而存在将数据库变成瓶颈的风险。
The Control Bus monitors the state of each component that processes messages, but it does not concern itself with the route that an individual message takes. You could modify each component to publish the unique message identifier of each message that passes through it to the Control Bus. This information can then be collected in a common database, a Message Store. This approach requires a significant amount of infrastructure, including a separate datastore. Also, if a component needs to examine the history of a message, it would have to execute a query against a central database, running the risk of turning the database into a bottleneck.
跟踪系统中的消息流并不像看上去那么简单。使用与每条消息关联的唯一消息 ID 似乎很自然。然而,当组件(例如,消息路由器)处理消息并将其发布到输出通道时,生成的消息将接收与该组件消耗的消息不关联的新消息标识符。因此,我们需要识别从传入消息复制到传出消息的新密钥,以便稍后可以将这两个消息关联起来。如果组件为其使用的每条消息只发布一条消息,则这种方法可以很好地工作。但是,许多组件的情况并非如此,例如收件人列表、聚合器或流程管理器,它们通常发布多条消息以响应单个输入消息。
Tracking the flow of a message through a system is not as simple as it appears. It would seem natural to use the unique message ID associated with each message. However, when a component (e.g., a Message Router) processes a message and publishes it to the output channel, the resulting message will receive a new message identifier that is not associated with the message that the component consumed. Therefore, we would need to identify a new key that is copied from the incoming message to the outgoing message so that the two messages can be associated later. This can work reasonably well if the component publishes exactly one message for every message it consumes. However, this is not the case for many components, such as a Recipient List, an Aggregator, or a Process Manager, which typically publish multiple messages in response to a single input message.
消息本身可以收集它所遍历的组件列表,而不是通过标记消息来识别每条消息的路径。如果消息传递系统中的每个组件都带有唯一标识符,则每个组件都可以将其标识符添加到其发布的每条消息中。
Instead of identifying the path of each message by tagging the messages, the message itself could collect a list of components that it traversed. If each component in the messaging system carries a unique identifier, each component could add its identifier to each message it publishes.
|
将消息历史记录附加到消息中。消息历史记录是消息自发起以来所经过的所有应用程序或组件的列表。 Attach a Message History to the message. The Message History is a list of all applications or components that the message passed through since its origination. |
消息历史记录维护消息所经过的所有组件的列表。每个处理消息的组件(包括发起者)都会向列表中添加一个条目。消息历史记录应该是消息标头的一部分,因为它包含系统特定的控制信息。将此信息保留在标头中可将其与包含应用程序特定数据的消息正文分开。
The Message History maintains a list of all components that the message passed through. Every component that processes the message (including the originator) adds one entry to the list. The Message History should be part of the message header because it contains system-specific control information. Keeping this information in the header separates it from the message body that contains application-specific data.
并非组件发布的每条消息都是单个消息的结果。例如,聚合器发布一条消息,其中包含从多条消息收集的信息,每条消息都可以有自己的历史记录。如果我们想在消息历史记录中表示这种情况,我们有两种选择。如果我们想跟踪完整的历史记录,我们可以增强消息历史记录存储为分层树结构。由于树结构的递归性质,我们可以在单个节点下存储多个消息历史记录。或者,我们可以保留一个简单的列表并仅保留一条传入消息的历史记录。如果一条传入消息对结果而言比其他辅助消息更重要,则这种方法可以很好地发挥作用。例如,在拍卖场景中,我们可以选择仅传播“获胜”消息的历史记录。
Not every message that a component publishes is the result of a single message. For example, an Aggregator publishes a single message that carries information collected from multiple messages, each of which could have its own history. If we want to represent this scenario in the Message History, we have two choices. If we want to track the complete history, we can enhance the Message History to be stored as a hierarchical tree structure. Because of the recursive nature of a tree structure, we can store multiple message histories under a single node. Alternatively, we can keep a simple list and keep the history of only one incoming message. This approach can work well if one incoming message is more important to the result than other, auxiliary messages. For example, in an auction scenario we could choose to propagate only the history of the "winning" message.
如果一系列消息流经多个不同的过滤器,这些过滤器共同执行特定的业务功能或流程,则消息历史记录非常有用。如果管理消息所采用的路径很重要,那么流程管理器可能会很有用。流程管理器为每个传入的触发消息创建一个流程实例。现在,可以集中管理通过各个组件的消息流,从而无需使用其历史记录来标记每条消息。
The Message History is most useful if a series of messages flows through a number of different filters that together perform a specific business function or process. If it is important to manage the path that a message takes, a Process Manager can be useful. The Process Manager creates one process instance for each incoming trigger message. The flow of the message through various components is now managed centrally, alleviating the need to tag each message with its history.
|
示例: 避免无限循环 Example: Avoiding Infinite Loops 使用发布-订阅通道传播事件时,为消息配备其历史记录还有另一个重要的好处。假设我们实现一个通过发布-订阅通道将地址更改传播到多个系统的系统。每个地址更改都会广播到所有感兴趣的系统,以便它们可以更新其记录。这种方法对于添加新系统非常灵活,新系统将自动接收广播消息,而不需要对现有消息系统进行任何更改。假设客户服务系统是在应用程序数据库中存储地址的系统之一。对数据库字段的每次更改都会触发一条消息,以通知所有系统该更改。根据发布-订阅范例的性质,订阅地址更改通道的所有系统都将接收该事件。然而,客户服务系统本身也必须订阅该频道,以便接收其他系统中所做的更新,例如通过自助服务网站。这意味着客户服务系统将收到它刚刚发布的消息。收到的消息将导致数据库更新,进而触发另一条地址更改消息。我们可能会陷入地址更改消息的无限循环中。为了避免这种无限循环,订阅应用程序可以检查消息历史记录,用于确定消息是否源自同一系统,如果是这种情况,则忽略传入消息。 Equipping a message with its history has another important benefit when using Publish-Subscribe Channels to propagate events. Assume we implement a system that propagates address changes to multiple systems via Publish-Subscribe Channels. Each address change is broadcast to all interested systems so that they may update their records. This approach is very flexible toward the addition of new systemsthe new system will automatically receive the broadcast message without requiring any changes to the existing messaging system. Assume the customer care system is one of the systems that stores addresses in the application database. Each change to the database fields causes a message to be triggered to notify all systems of the change. By nature of the publish-subscribe paradigm, all systems subscribing to the address-changed channel will receive the event. However, the customer care system itself has to subscribe to this channel as well in order to receive updates made in other systems, for example through a self-service Web site. This means that the customer care system will receive the message that it just published. This received message would result in a database update, which will in turn trigger another address-changed message. We could end up in an infinite loop of address-changed messages. To avoid such an infinite loop, the subscribing applications can inspect the Message History to determine whether the message originated from the very same system and ignore the incoming message if this is the case. |
|
示例: TIBCO ActiveEnterprise Example: TIBCO ActiveEnterprise 许多 EAI 集成套件都包含对消息历史记录的支持。例如,每个 TIBCO ActiveEnterprise 消息的消息标头都包含一个跟踪字段,该字段维护消息所经过的所有组件的列表。在这种情况下,需要注意的是,TIBCO ActiveEnterprise 组件为传出消息分配与使用的消息相同的消息 ID。这使得通过多个组件跟踪消息变得更容易,但这也意味着消息 ID 不是系统范围内的唯一属性,因为多个单独的消息共享相同的 ID。例如,在实现收件人列表时,TIBCO ActiveEnterprise 将使用的消息的 ID 传输到每个出站消息。 Many EAI integration suites include support for a Message History. For example, the message header of every TIBCO ActiveEnterprise message includes a tracking field that maintains a list of all components through which the message has passed. In this context it is important to note that a TIBCO ActiveEnterprise component assigns outgoing messages the same message ID as the consumed message. This makes tracking messages through multiple components easier, but it also means that the message ID is not a systemwide unique property, because multiple individual messages share the same ID. For example, when implementing a Recipient List, TIBCO ActiveEnterprise transfers the ID of the consumed message to each outbound message. 以下示例显示了通过多个组件传递的消息的内容,包括两个名为OrderProcess 和verifyCustomerStub 的IntegrationManager 进程。 The following example shows the contents of a message that passed through multiple components, including two IntegrationManager processes, named OrderProcess and VerifyCustomerStub. tw.training.customer.verify.response { RVMSG_INT 2 ^pfmt^ 10 RVMSG_INT 2 ^版本^ 30 RVMSG_INT 2 ^类型^ 1 RVMSG_RVMSG 108 ^数据^ { RVMSG_STRING 23 ^class^“验证客户响应” RVMSG_INT 4 ^idx^ 1 RVMSG_STRING 6 CUSTOMER_ID“12345” RVMSG_STRING 6 ORDER_ID“22222” RVMSG_INT 4 结果 0 } RVMSG_RVMSG 150 ^追踪^ { RVMSG_STRING 28 ^id^ "4OEaDEoiBIpcYk6qihzzwB5Uzzw" RVMSG_STRING 41 ^1^ tw.training.customer.verify.response { RVMSG_INT 2 ^pfmt^ 10 RVMSG_INT 2 ^ver^ 30 RVMSG_INT 2 ^type^ 1 RVMSG_RVMSG 108 ^data^ { RVMSG_STRING 23 ^class^ "VerifyCustomerResponse" RVMSG_INT 4 ^idx^ 1 RVMSG_STRING 6 CUSTOMER_ID "12345" RVMSG_STRING 6 ORDER_ID "22222" RVMSG_INT 4 RESULT 0 } RVMSG_RVMSG 150 ^tracking^ { RVMSG_STRING 28 ^id^ "4OEaDEoiBIpcYk6qihzzwB5Uzzw" RVMSG_STRING 41 ^1^ "imed_debug_engine1-OrderProcess-Job-4300" RVMSG_STRING 47 ^2^ "imed_debug_engine1-VerifyCustomerStub-Job-4301" } } |
正如消息历史记录所述,松散耦合的架构原则允许解决方案具有灵活性,但可能会导致难以深入了解集成解决方案的动态行为。
As the Message History describes, the architectural principle of loose coupling allows for flexibility in the solution but can make it difficult to gain insight into the dynamic behavior of the integration solution.
|
我们如何在不影响消息传递系统的松散耦合和瞬态性质的情况下报告消息信息? How can we report against message information without disturbing the loosely coupled and transient nature of a messaging system? |
使消息传递功能强大的特性也可能使其难以管理。异步消息传递保证传递,但不保证消息何时传递。然而,对于许多实际应用来说,系统的响应时间可能至关重要。此外,虽然异步消息传递单独处理每个消息,但跨越多个消息的信息(例如,在特定时间间隔内通过系统的消息数量)可能非常有用。
The very properties that make messaging powerful can also make it difficult to manage. Asynchronous messaging guarantees delivery but does not guarantee when the message will be delivered. For many practical applications, though, the response time of a system may be critical. Also, while asynchronous messaging treats each message individually, information that spans multiple messagesfor example, the number of messages passing through the system within a certain time intervalcan be very useful.
消息历史模式说明了能够辨别消息“来源”的有用性。从这些数据中,我们可以得出有趣的消息吞吐量和运行时统计数据。唯一的缺点是信息包含在每条单独的消息中。没有简单的方法可以报告此信息,因为它分布在许多消息中。此外,消息的生命周期可能非常短。一旦消息被消费,消息历史记录可能不再可用。
The Message History pattern illustrates the usefulness of being able to tell the "source" of a message. From this data, we can derive interesting message throughput and runtime statistics. The only downside is that the information is contained within each individual message. There is no easy way to report against this information, since it is spread across many messages. Also, the lifetime of a message can be very short. Once the message is consumed, the Message History may no longer be available.
为了执行有意义的报告,我们需要将消息数据持久存储在中央位置。
In order to perform meaningful reporting, we need to store message data persistently and in a central location.
|
使用消息存储在中央位置捕获有关每条消息的信息。 Use a Message Store to capture information about each message in a central location. |
使用消息存储时,我们可以利用消息传递基础设施的异步特性。当我们向通道发送消息时,我们会将消息的副本发送到特殊通道以由消息存储收集。这可以由组件本身执行,或者我们可以将丝锥插入通道中。我们可以将携带消息副本的辅助通道视为控制总线的一部分。以“即发即忘”模式发送第二条消息不会减慢主应用程序消息的传输速度。然而,它确实增加了网络流量。这就是为什么我们可能不存储完整的消息,而只存储稍后分析所需的几个关键字段,例如消息 ID 或发送消息的通道与时间戳相结合。
When using a Message Store, we can take advantage of the asynchronous nature of a messaging infrastructure. When we send a message to a channel, we send a duplicate of the message to a special channel to be collected by the Message Store. This can be performed by the component itself, or we can insert a Wire Tap into the channel. We can consider the secondary channel that carries a copy of the message as part of the Control Bus. Sending a second message in a "fire-and-forget" mode will not slow down the flow of the main application messages. It does, however, increase network traffic. That's why we may not store the complete message but just a few key fields that are required for later analysis, such as a message ID or the channel on which the message was sent combined with a timestamp.
存储多少细节实际上是一个重要的考虑因素。显然,我们掌握的每条消息的数据越多,我们的报告能力就越好。反作用力是网络流量和消息存储的存储容量。即使我们存储所有消息数据,我们的报告能力仍然可能受到限制。消息通常共享相同的消息头结构,但每种消息类型的消息正文的结构都不同,并且外部应用程序可能难以访问(例如,消息正文可能包含序列化的 Java 对象)。这可能使得报告消息正文中包含的数据元素变得困难。
How much detail to store is actually an important consideration. Obviously, the more data we have about each message, the better reporting abilities we have. The counterforces are network traffic and storage capacity of the Message Store. Even if we store all message data, our reporting abilities may still be limited. Messages typically share the same message header structure, but the message body is structured differently for each type of message and can be difficult to access by outside applications (for example, the message body might contain a serialized Java object). This can make it difficult to report against the data elements contained in the message body.
由于消息正文中的数据对于每种类型的消息可以采用不同的格式,因此我们需要考虑不同的存储选项。如果我们创建一个单独的存储模式(例如,表)来匹配每种消息类型的内部数据结构,我们可以应用索引并对消息内容执行复杂的搜索。然而,这假设我们为每种消息类型都有一个单独的存储结构。这可能很快就会变成维护负担。相反,我们可以将消息数据作为 XML 格式的非结构化数据存储在长字符字段中。这允许我们使用通用存储模式。我们仍然可以查询标头字段,但无法根据消息正文中的字段进行报告。然而,一旦我们确定了一条特定的消息,消息存储。或者,我们可以使用 XML 存储库来存储消息。这些类型的存储库索引 XML 文档以供以后检索和分析。
Since the data inside the message body can be formatted differently for each type of message, we need to consider different storage options. If we create a separate storage schema (e.g., tables) to match each message type's internal data structure, we can apply indexes and perform complex searches on the message content. However, this assumes that we have a separate storage structure for each message type. This could quickly turn into a maintenance burden. Instead, we could store the message data as unstructured data in XML format in a long character field. This allows us to use a generic storage schema. We could still query against header fields but would not be able to report against fields in the message body. However, once we identified a specific message, we could recreate the message content based on the XML document stored in the Message Store. Alternatively, we could use an XML repository to store the messages. These types of repositories index XML documents for later retrieval and analysis.
消息存储可能会变得非常大,因此我们很可能需要引入清除机制。此机制可以将较旧的消息日志移至备份数据库或将其完全删除。
The Message Store may get very large, so most likely we will need to introduce a purging mechanism. This mechanism could move older message logs to a backup database or delete them altogether.
|
示例: 商业 EAI 工具 Example: Commercial EAI Tools 一些企业集成工具提供消息存储。例如,MSMQ 允许队列自动将发送或接收的消息存储在Journal Queue中。Microsoft BizTalk 可以选择将所有文档(消息)存储在 SQL Server 数据库中以供以后分析。 Some enterprise integration tools supply a Message Store. For example, MSMQ allows queues to automatically store sent or received messages in a Journal Queue. Microsoft BizTalk optionally stores all documents (messages) in a SQL Server database for later analysis. |
一对Wire Tap可用于跟踪流经组件的消息,但此方法假设组件将消息发布到固定的输出通道。然而,许多服务样式组件将回复消息发布到请求消息中包含的返回地址指定的通道。
A pair of Wire Taps can be used to track messages that flow through a component, but this approach assumes that the component publishes messages to a fixed output channel. However, many service-style components publish reply messages to the channel specified by the Return Address included in the request message.
|
如何跟踪向请求者指定的返回地址发布回复消息的服务上的消息? How can you track messages on a service that publishes reply messages to the Return Address specified by the requestor? |
为了跟踪流经服务的消息,我们需要捕获请求和回复消息。使用Wire Tap拦截请求消息非常简单。拦截回复消息是最困难的部分,因为服务根据请求者的首选返回地址将回复消息发布到不同的通道。
In order to track messages flowing through a service, we need to capture both request and reply messages. Intercepting a request message using a Wire Tap is easy enough. Intercepting reply messages is the tough part because the service publishes the reply message to different channels based on the requestor's preferred Return Address.
大多数请求返回地址的支持,以便请求者可以指定回复消息应发送到的通道。更改服务以将回复消息发布到固定通道将使每个请求者很难提取正确的回复消息。某些消息传递系统允许消费者在单个回复队列中查看特定消息,但该方法是特定于实现的,并且在回复消息不返回到请求者而是返回到第三方的情况下不起作用。
The support of a Return Address is required for most Request-Reply services so that a requestor can specify the channel that the reply message should be sent to. Changing the service to post reply messages to a fixed channel would make it hard for each requestor to extract the correct reply messages. Some messaging systems allow consumers to peek for specific messages inside a single reply queue, but that approach is implementation-specific and does not work in those instances where the reply message does not go back to the requestor but to a third party.
正如Wire Tap中所讨论的,修改组件来检查消息并不总是可行或实用的。如果我们正在处理打包的应用程序,我们可能无法修改应用程序代码,并且可能必须实现应用程序外部的解决方案。同样,我们可能不希望要求每个应用程序实现消息检查逻辑,特别是因为逻辑的性质可能会根据我们是在测试模式还是生产模式下操作而有所不同。将检查功能保留在单独的、独立的组件中可以提高灵活性、重用性和可测试性。
As discussed in the Wire Tap, modifying the component to inspect messages is not always feasible or practical. If we are dealing with a packaged application, we may not be able to modify the application code and may have to implement a solution that is external to the application. Likewise, we may not want to require each application to implement message inspection logic, especially because the nature of the logic may vary depending on whether we operate in test mode or production mode. Keeping the inspection functions in a separate, self-contained component improves flexibility, reuse, and testability.
|
使用智能代理存储原始请求者提供的返回地址,并将其替换为智能代理的地址。当服务发送回复消息时,将其路由到原始返回地址。 Use a Smart Proxy to store the Return Address supplied by the original requestor and replace it with the address of the Smart Proxy. When the service sends the reply message, route it to the original Return Address. |
智能代理拦截在请求通道上发送到请求-答复服务的消息。对于每条传入消息,智能代理都会存储原始发件人指定的返回地址。然后,它将消息中的返回地址替换为智能代理正在侦听的回复通道。当回复消息进入该通道时,智能代理会执行任何所需的分析功能,检索存储的返回地址,并使用消息。
The Smart Proxy intercepts messages sent on the request channel to the Request-Reply service. For each incoming message, the Smart Proxy stores the Return Address specified by the original sender. It then replaces the Return Address in the message with the reply channel that the Smart Proxy is listening on. When a reply message comes in on that channel, the Smart Proxy performs any desired analytical functions, retrieves the stored Return Address, and forwards the unmodified reply message to the original reply channel by using a Message Router.
当外部服务不支持返回地址而是回复固定回复通道时,智能代理也很有用。我们可以使用智能代理来代理此类服务,以提供对返回地址的支持。在这种情况下,智能代理不执行任何分析功能,而只是将回复消息转发到正确的通道。
The Smart Proxy is also useful in cases where an external service does not support a Return Address but instead replies to a fixed reply channel. We can proxy such a service with a Smart Proxy to provide support for a Return Address. In this case, the Smart Proxy performs no analytical functions but simply forwards the reply message to the correct channel.
智能代理需要存储原始请求者提供的返回地址,以便能够将传入的回复消息与返回地址相关联,并将回复消息转发到正确的通道。智能代理可以将这些数据存储在两个地方:
The Smart Proxy needs to store the Return Address supplied by the original requestor in such a way that it can correlate incoming reply messages with the Return Address and forward the reply message to the correct channel. The Smart Proxy can store this data in two places:
消息里面
Inside the message
智能代理内部
Inside the Smart Proxy
为了将返回地址存储在消息中,智能代理可以将新的消息字段与返回地址一起添加到消息中。请求-回复服务需要将此字段复制到回复消息中。智能代理所要做的就是从回复消息中提取特殊的消息字段,从消息中删除该字段,并将消息转发到该字段指定的通道。该解决方案使智能代理保持简单,但它需要请求-答复服务的协作。如果请求-回复服务是不可修改的组件,此选项可能不可用。
To store the Return Address inside the message, the Smart Proxy can add a new message field together with the Return Address to the message. The Request-Reply service is required to copy this field to the reply message. All the Smart Proxy has to do is extract the special message field from the reply message, remove the field from the message, and forward the message to the channel specified by the field. This solution keeps the Smart Proxy simple, but it requires collaboration by the Request-Reply service. If the Request-Reply service is a nonmodifiable component, this option may not be available.
或者,智能代理可以将返回地址存储在专用存储器中,例如存储在存储器结构或关系数据库中。由于智能代理的目的是跟踪请求和回复消息之间的消息,因此智能代理通常必须存储请求消息中的数据,以将其与回复消息关联起来,以便可以同时分析这两个消息。这种方法要求智能代理能够将回复消息与响应消息相关联。大多数请求-答复服务支持关联标识符服务从请求消息复制到回复消息。如果智能代理无法修改原始消息格式,它可以使用(或滥用)此字段来关联请求和回复消息。
Alternatively, the Smart Proxy can store the Return Address in dedicated storage, for example, in a memory structure or a relational database. Because the purpose of the Smart Proxy is to track messages between the request and reply message, the Smart Proxy usually has to store data from the request message anyway to correlate it to the reply message so that both messages can be analyzed in unison. This approach requires the Smart Proxy to be able to correlate the reply message back to the response message. Most Request-Reply services support a Correlation Identifier that the service copies from the request message to the reply message. If the Smart Proxy cannot modify the original message format, it can use (or abuse) this field to correlate request and reply messages.
然而,智能代理最好构建自己的相关标识符,因为并非所有请求者都会指定相关标识符,而且所提供的相关标识符只需要在单个请求者发出的请求中是唯一的,并且可能不是唯一的跨多个请求者。由于从服务到智能代理的单个服务回复队列现在携带来自多个请求者的消息,因此使用原始相关标识符并不可靠。因此,智能代理存储原始的相关标识符与原始返回地址一起,并用自己的相关标识符替换原始相关标识符,以便在回复消息到达时可以检索原始相关标识符和返回地址。
However, it is better for the Smart Proxy to construct its own Correlation Identifier, because not all requestors will specify a Correlation Identifier and also because the supplied Correlation Identifier only needs to be unique only across requests made by a single requestor and may not be unique across multiple requestors. Because the single service reply queue from the service to the Smart Proxy now carries messages from multiple requestors, using the original Correlation Identifier is not reliable. Therefore, the Smart Proxy stores the original Correlation Identifier together with the original Return Address and replaces the original Correlation Identifier with its own Correlation Identifier so that it can retrieve the original Correlation Identifier and Return Address when the reply message arrives.
某些服务使用请求消息的消息 ID 作为回复消息的相关标识符。这引入了另一个问题。该服务现在将从智能代理接收到的请求消息的消息 ID 复制到智能代理的回复消息中。智能代理需要将回复消息中的关联标识符替换为原始请求消息的消息ID,以便请求者能够正确关联请求和回复消息。下页的图说明了此过程。
Some services use the message ID of the request message as the Correlation Identifier for the reply message. This introduces another problem. The service will now copy the message ID of the request message it received from the Smart Proxy to the reply message to the Smart Proxy. The Smart Proxy needs to replace this Correlation Identifier in the reply message with the message ID of the original request message so that the requestor can properly correlate request and reply messages. The figure on the following page illustrates this process.
值得注意的是,所有四个消息都具有唯一的消息 ID,即使它们与单个“逻辑”消息流相关。
It is important to note that all four messages have unique message IDs even though they relate to the flow of a single "logical" message.
存储和替换相关标识符和返回地址
Storing and Replacing the Correlation Identifier and Return Address
|
示例: MSMQ 和 C# 中的简单智能代理 Example: Simple Smart Proxy in MSMQ and C# 实施智能代理并不像听起来那么复杂。以下代码实现了一个由两个请求者、一个智能代理和一个简单服务组成的解决方案场景。智能代理将消息处理时间传递到控制总线以在控制台中显示。我们希望允许请求者通过消息 ID 或Message对象提供的数字AppSpecific属性进行关联。 Implementing a Smart Proxy is not as complicated as it sounds. The following code implements a solution scenario consisting of two requestors, a Smart Proxy and a simple service. The Smart Proxy passes the message processing time to the control bus for display in the console. We want to allow the requestors to correlate by either the message ID or the numeric AppSpecific property provided by the Message object. 简单的智能代理示例 Simple Smart Proxy Example 为了方便编码,我们定义了一个基类MessageConsumer,它封装了创建事件驱动消息使用者所需的代码。继承类可以简单地重载虚拟方法ProcessMessage来执行任何必要的消息处理,并且它们不必担心消息队列的配置或事件驱动的处理。将此代码分离到一个公共基类中,只需几行代码就可以轻松创建测试客户端和虚拟请求-答复服务。 For our coding convenience, we define a base class MessageConsumer that encapsulates the code that is required to create an event-driven message consumer. Inheriting classes can simply overload the virtual method ProcessMessage to perform any necessary message handling, and they do not have to worry about the configuration of the message queue or the event-driven processing. Separating this code into a common base class makes it easy to create test clients and a dummy Request-Reply service with just a few lines of code. 消息消费者公共类消息消费者
{ 受保护的消息队列输入队列;
公共消息消费者(消息队列输入队列)
{
this.inputQueue = inputQueue;
SetupQueue(this.inputQueue);
Console.WriteLine(this.GetType().Name + ": 处理中
public class MessageConsumer
{ protected MessageQueue inputQueue;
public MessageConsumer (MessageQueue inputQueue)
{
this.inputQueue = inputQueue;
SetupQueue(this.inputQueue);
Console.WriteLine(this.GetType().Name + ": Processing
messages from " +
inputQueue.Path);
}
protected void SetupQueue(MessageQueue queue)
{
queue.Formatter = new System.Messaging.XmlMessageFormatter
(new String[] {"System.String
,mscorlib"});
queue.MessageReadPropertyFilter.ClearAll();
queue.MessageReadPropertyFilter.AppSpecific = true;
queue.MessageReadPropertyFilter.Body = true;
queue.MessageReadPropertyFilter.CorrelationId = true;
queue.MessageReadPropertyFilter.Id = true;
queue.MessageReadPropertyFilter.ResponseQueue = true;
}
public virtual void Process()
{
inputQueue.ReceiveCompleted +=
new ReceiveCompletedEventHandler(OnReceiveCompleted);
inputQueue.BeginReceive();
}
private void OnReceiveCompleted(Object source,
ReceiveCompletedEventArgs asyncResult)
{
MessageQueue mq = (MessageQueue)source;
Message m = mq.EndReceive(asyncResult.AsyncResult);
m.Formatter = new System.Messaging.XmlMessageFormatter
(new String[] {"System.String,mscorlib"});
ProcessMessage(m);
mq.BeginReceive();
}
protected virtual void ProcessMessage(Message m)
{
String text = "";
try
{
text = (String)m.Body;
}
catch (InvalidOperationException) {};
Console.WriteLine(this.GetType().Name + ": Received
Message " + text);
}
}
以MessageConsumer 类作为起点,我们可以创建一个智能代理。智能代理包含两个MessageConsumer ,一个用于来自请求者的请求消息 ( SmartProxyRequestConsumer ),另一个用于由请求-回复服务返回的回复消息 ( SmartProxyReplyConsumer ) 。智能代理还定义了一个哈希表来存储请求和回复消息之间的消息数据。 With the MessageConsumer class as a starting point, we can create a Smart Proxy. A Smart Proxy contains two MessageConsumers, one for the request messages coming from the requestors (SmartProxyRequestConsumer) and one for the reply messages returned by the Request-Reply service (SmartProxyReplyConsumer). The Smart Proxy also defines a Hashtable to store message data between request and reply messages. 智能代理公共类 SmartProxyBase
{
受保护的 SmartProxyRequestConsumer 请求消费者;
受保护的 SmartProxyReplyConsumer 回复Consumer;
受保护的哈希表消息数据;
公共SmartProxyBase(消息队列输入队列,
消息队列服务请求队列,
消息队列服务回复队列)
{
messageData = Hashtable.Synchronized(new Hashtable());
requestConsumer = 新的 SmartProxyRequestConsumer
public class SmartProxyBase
{
protected SmartProxyRequestConsumer requestConsumer;
protected SmartProxyReplyConsumer replyConsumer;
protected Hashtable messageData;
public SmartProxyBase(MessageQueue inputQueue,
MessageQueue serviceRequestQueue,
MessageQueue serviceReplyQueue)
{
messageData = Hashtable.Synchronized(new Hashtable());
requestConsumer = new SmartProxyRequestConsumer
(inputQueue, serviceRequestQueue,
serviceReplyQueue, messageData);
replyConsumer = new SmartProxyReplyConsumer
(serviceReplyQueue, messageData);
}
public virtual void Process()
{
requestConsumer.Process();
replyConsumer.Process();
}
}
SmartProxyRequestConsumer相对简单。它将请求消息中的相关信息(消息 ID、返回地址、 AppSpecific属性和当前时间)存储在哈希表中,并通过发送到实际服务的新请求消息的消息 ID 进行索引。请求-答复服务通过将此消息 ID复制到服务答复消息的CorrelationID 字段来支持相关标识符。这允许智能代理在回复消息到达时检索存储的消息数据。SmartProxyRequestConsumer还替换了返回地址ResponseQueue属性以及智能代理侦听回复消息的队列。我们在此类中包含了一个虚拟方法AnalyzeMessage ,以便子类可以执行任何所需的分析。 The SmartProxyRequestConsumer is relatively simple. It stores relevant information from the request message (message ID, the Return Address, the AppSpecific property, and the current time) in the hashtable, indexed by the message ID of the new request message sent to the actual service. The request-reply service supports the Correlation Identifier by copying this message ID to the CorrelationID field of the service reply message. This allows the Smart Proxy to retrieve the stored message data when the reply message arrives. The SmartProxyRequestConsumer also replaces the Return Address the ResponseQueue propertywith the queue that the Smart Proxy listens on for reply messages. We included a virtual method AnalyzeMessage in this class so that subclasses can perform any desired analysis. 智能代理请求消费者公共类 SmartProxyRequestConsumer :MessageConsumer
{
受保护的哈希表消息数据;
受保护的消息队列服务请求队列;
受保护的消息队列服务回复队列;
公共SmartProxyRequestConsumer(消息队列请求队列,
消息队列
public class SmartProxyRequestConsumer : MessageConsumer
{
protected Hashtable messageData;
protected MessageQueue serviceRequestQueue;
protected MessageQueue serviceReplyQueue;
public SmartProxyRequestConsumer(MessageQueue requestQueue,
MessageQueue
serviceRequestQueue,
MessageQueue serviceReplyQueue,
Hashtable messageData) :
base(requestQueue)
{
this.messageData = messageData;
this.serviceRequestQueue = serviceRequestQueue;
this.serviceReplyQueue = serviceReplyQueue;
}
protected override void ProcessMessage(Message requestMsg)
{
base.ProcessMessage(requestMsg);
MessageData data = new MessageData(requestMsg.Id,
requestMsg.ResponseQueue,
requestMsg.AppSpecific);
requestMsg.ResponseQueue = serviceReplyQueue;
serviceRequestQueue.Send(requestMsg);
messageData.Add(requestMsg.Id, data);
AnalyzeMessage(requestMsg);
}
protected virtual void AnalyzeMessage(Message requestMsg)
{
}
}
SmartProxyReplyConsumer侦听服务回复通道。此类的ProcessMessage方法检索SmartProxyRequestConsumer,并调用AnalyzeMessage模板方法。然后,它将CorrelationID和AppSpecific属性复制到新的回复消息中,并将其路由到原始请求消息中指定的返回地址。 The SmartProxyReplyConsumer listens on the service reply channel. The ProcessMessage method of this class retrieves the message data for the associated request message stored by the SmartProxyRequestConsumer and calls the AnalyzeMessage template method. It then copies the CorrelationID and the AppSpecific properties to the new reply message and routes it to the Return Address specified in the original request message. 智能代理回复消费者公共类 SmartProxyReplyConsumer :MessageConsumer
{
受保护的哈希表消息数据;
公共SmartProxyReplyConsumer(消息队列回复队列,
哈希表 messageData) : 基础
public class SmartProxyReplyConsumer : MessageConsumer
{
protected Hashtable messageData;
public SmartProxyReplyConsumer(MessageQueue replyQueue,
Hashtable messageData) : base
(replyQueue)
{
this.messageData = messageData;
}
protected override void ProcessMessage(Message replyMsg)
{
base.ProcessMessage(replyMsg);
String corr = replyMsg.CorrelationId;
if (messageData.Contains(corr))
{
MessageData data = (MessageData)(messageData[corr]);
AnalyzeMessage(data, replyMsg);
replyMsg.CorrelationId = data.CorrelationID;
replyMsg.AppSpecific = data.AppSpecific;
MessageQueue outputQueue = data.ReturnAddress;
outputQueue.Send(replyMsg);
messageData.Remove(corr);
}
else
{
Console.WriteLine(this.GetType().Name + "Unrecognized
Reply Message");
//send message to invalid message queue
}
}
protected virtual void AnalyzeMessage(MessageData data,
Message replyMessage)
{
}
}
为了收集指标并将其发送到控制总线,我们对通用SmartProxy和SmartProxyReplyConsumer 类进行了子类化。新的MetricsSmartProxy将SmartProxyReplyConsumerMetrics 实例化为使用。此类包括AnalyzeMessage方法的简单实现,该方法计算请求和响应之间的消息运行时间,并将此数据与未完成消息的数量一起发送到控制总线队列。我们可以轻松地增强此方法以执行更复杂的计算。控制总线队列连接到一个简单的文件编写器,该文件编写器将每个传入消息写入文件。 In order to collect metrics and send them to the control bus, we subclass both the generic SmartProxy and SmartProxyReplyConsumer classes. The new MetricsSmartProxy instantiates the SmartProxyReplyConsumerMetrics as the class consuming reply messages. This class includes a simple implementation of the AnalyzeMessage method that computes the message runtime between request and response and sends this data together with the number of outstanding messages to the Control Bus queue. We could easily enhance this method to perform more complex computations. The Control Bus queue is connected to a simple file writer that writes each incoming message to a file. 指标SmartProxy公共类 MetricsSmartProxy :SmartProxyBase
{
公共 MetricsSmartProxy(MessageQueue inputQueue,
消息队列服务请求队列,
消息队列服务回复队列,
消息队列控制总线):
基(输入队列,服务请求队列,
public class MetricsSmartProxy : SmartProxyBase
{
public MetricsSmartProxy(MessageQueue inputQueue,
MessageQueue serviceRequestQueue,
MessageQueue serviceReplyQueue,
MessageQueue controlBus) :
base (inputQueue, serviceRequestQueue,
serviceReplyQueue)
{
replyConsumer = new SmartProxyReplyConsumerMetrics
(serviceReplyQueue, messageData,
controlBus);
}
}
SmartProxyReplyConsumerMetrics公共类 SmartProxyReplyConsumerMetrics :SmartProxyReplyConsumer
{
消息队列控制总线;
公共 SmartProxyReplyConsumerMetrics(消息队列回复队列,
哈希表消息数据,
消息队列控制总线):
基础(回复队列,消息数据)
{
this.controlBus = controlBus;
}
受保护的覆盖无效AnalyzeMessage(MessageData数据,
public class SmartProxyReplyConsumerMetrics : SmartProxyReplyConsumer
{
MessageQueue controlBus;
public SmartProxyReplyConsumerMetrics(MessageQueue replyQueue,
Hashtable messageData,
MessageQueue controlBus) :
base(replyQueue, messageData)
{
this.controlBus = controlBus;
}
protected override void AnalyzeMessage(MessageData data,
Message replyMessage)
{
TimeSpan duration = DateTime.Now - data.SentTime;
Console.WriteLine(" processing time: {0:f}", duration
.TotalSeconds);
if (controlBus != null)
{
controlBus.Send(duration.TotalSeconds.ToString() + "
," + messageData.Count);
}
}
}
下面的类图显示了各个类之间的关系: The following class diagram shows the relationship between the individual classes: 智能代理示例类图 Smart Proxy Example Class Diagram 为了测试代理,我们创建了一个虚拟的请求-答复服务,该服务除了等待 0 到 200 毫秒之间的随机间隔外什么也不做。我们从两个请求者向智能代理提供数据,每个请求者以 100 毫秒的间隔发布 30 条消息。我们将控制总线队列上的消息捕获到日志文件中。出于演示目的,我们将生成的控制总线文件加载到 Microsoft Excel 电子表格中,并创建了一个漂亮的图表。 To test the proxy, we created a dummy request-reply service that does nothing but wait for a random interval between 0 and 200 ms. We feed the Smart Proxy from two requestors, each of which publishes 30 messages in 100 ms intervals. We capture the messages on the Control Bus queue into a log file. For demonstration purposes, we loaded the resulting control bus file into a Microsoft Excel spreadsheet and created a nice looking chart. 智能代理收集并由控制总线控制台可视化的响应时间统计数据 Response Time Statistics Collected by the Smart Proxy and Visualized by the Control Bus Console 我们可以看到队列大小和响应时间稳步增加,直到 13 条消息排队。此时,请求者停止发送新消息,使得队列大小稳步减小。响应时间也减少了,但仍保持在 1 秒左右,因为现在正在处理的消息已经在请求队列中等待了那么长时间。 We can see that the queue size and the response time increase steadily until 13 messages are queued up. At that time, the requestors stop sending new messages so that the queue size decreases steadily. The response time decreases as well, but remains around 1 second because the messages that are now being processed have been sitting in the request queue for that long. |
控制总线描述了多种监视消息处理系统健康状况的方法。系统中的每个组件都可以定期向控制总线发布心跳消息,以便让监控机制了解该组件仍然处于活动状态。心跳消息还可以包含组件的重要统计信息,例如处理的消息数量、处理消息所需的平均时间或计算机上的 CPU 使用率百分比。
The Control Bus describes a number of approaches to monitor the health of the message processing system. Each component in the system can publish periodic heartbeat messages to the Control Bus in order to keep the monitoring mechanism informed that the component is still active. The heartbeat messages can contain vital statistics of the component as well, such as the number of messages processed, the average time required to process a message, or the percentage of CPU utilization on the machine.
|
如果组件正在主动处理消息,但由于内部故障而导致传出消息出现乱码,会发生什么情况? What happens if a component is actively processing messages but garbles outgoing messages due to an internal fault? |
简单的心跳机制不会检测到此错误情况,因为它仅在组件级别运行并且不知道应用程序消息格式。
A simple heartbeat mechanism will not detect this error condition because it operates only at a component level and is not aware of application message formats.
|
将测试消息注入消息流以确认消息处理组件的运行状况。 Inject a Test Message into the message stream to confirm the health of message processing components. |
测试消息模式依赖于以下组件:
The Test Message pattern relies on the following components:
测试数据生成器创建要发送到组件进行测试的消息。测试数据可以是恒定的,由测试数据文件驱动,或者随机生成。
The Test Data Generator creates messages to be sent to the component for testing. Test data may be constant, driven by a test data file, or generated randomly.
测试消息注入器将测试数据插入到发送到组件的常规数据消息流中。注入器的主要作用是标记消息,以便区分真实应用程序消息和测试消息。这可以通过插入特殊的标头字段来完成。如果我们无法控制消息结构,我们可以尝试使用特殊值来指示测试消息(例如,OrderID = 999999)。这通过使用相同的字段来表示应用程序数据(实际订单号)和控制信息(这是测试消息)来改变应用程序数据的语义。因此,这种方法只能作为最后的手段使用。
The Test Message Injector inserts test data into the regular stream of data messages sent to the component. The main role of the injector is to tag messages in order to differentiate real application messages from test messages. This can be accomplished by inserting a special header field. If we have no control over the message structure, we can try to use special values to indicate test messages (e.g., OrderID = 999999). This changes the semantics of application data by using the same field to represent application data (the actual order number) and control information (this is a test message). Therefore, this approach should be used only as a last resort.
测试消息分隔符从输出流中提取测试消息的结果。这通常可以通过使用基于内容的路由器来完成。
The Test Message Separator extracts the results of test messages from the output stream. This can usually be accomplished by using a Content-Based Router.
测试数据验证器将实际结果与预期结果进行比较,如果发现差异,则标记异常。根据测试数据的性质,验证者可能需要访问原始测试数据。
The Test Data Verifier compares actual results with expected results and flags an exception if a discrepancy is discovered. Depending on the nature of the test data, the verifier may need access to the original test data.
如果被测组件支持返回地址,则可能不需要显式测试消息分隔符。在这种情况下,测试数据生成器可以包括一个特殊的测试通道作为返回地址,以便测试消息不会通过系统的其余部分传递。实际上,返回地址充当区分测试消息和应用程序消息的标签。
An explicit Test Message Separator may not be needed if the component under test supports a Return Address. In this case, the test Data Generator can include a special test channel as the Return Address so that test messages are not passed through the remainder of the system. Effectively, the Return Address acts as the tag that distinguishes test messages from application messages.
测试消息被认为是一种主动监控机制。与被动机制不同,主动机制不依赖于组件生成的信息(例如日志文件或心跳消息),而是主动探测组件。优点是主动监控通常可以实现更深层次的测试,因为数据通过与应用程序消息相同的处理步骤进行路由。它还可以与并非设计用于支持被动监控的组件配合良好。
Test Message is considered an active monitoring mechanism. Unlike passive mechanisms, active mechanisms do not rely on information generated by the components (e.g., log files or heartbeat messages) but actively probe the component. The advantage is that active monitoring usually achieves a deeper level of testing, since data is routed through the same processing steps as the application messages. It also works well with components that were not designed to support passive monitoring.
主动监控的一个可能的缺点是处理单元上的额外负载。我们需要在测试频率和最小化性能影响之间找到平衡。如果我们按按次付费使用组件,主动监控也可能会产生成本。许多外部组件都是这种情况,例如,如果我们向外部信用评分机构请求客户的信用报告。
One possible disadvantage of active monitoring is the additional load placed on the processing unit. We need to find a balance between frequency of test and minimizing the performance impact. Active monitoring may also incur cost if we are being charged for the use of a component on a pay-per-use basis. This is the case for many external componentsfor example, if we request credit reports for our customers from an external credit scoring agency.
主动监控并不适用于所有组件。有状态组件可能无法区分测试数据和真实数据,并且可能为测试数据创建数据库条目。我们可能不希望将测试订单包含在我们的年度收入报告中!
Active monitoring does not work with all components. Stateful components may not be able to distinguish test data from real data and may create database entries for test data. We may not want to have test orders included in our annual revenue report!
|
示例: 贷款经纪人:测试信用局 Example: Loan Broker: Testing the Credit Bureau 在第 12 章“插曲:系统管理示例”中,我们使用测试消息来主动监控外部信用局。 In Chapter 12, "Interlude: Systems Management Example," we use a Test Message to actively monitor the external credit bureau. |
当我们研究 JMS 请求-答复示例时(请参阅第 6 章“插曲:简单消息传递”),我们遇到了一个简单但有趣的问题。该示例包含一个请求者,该请求者向回复者发送消息并等待响应。该示例使用两个点对点通道: RequestQueue和ReplyQueue (见图)。
When we worked on the JMS request-reply example (see Chapter 6, "Interlude: Simple Messaging"), we ran into a simple but interesting problem. The example consists of a requestor that sends a message to a replier and waits for the response. The example uses two Point-to-Point Channels, RequestQueue and ReplyQueue (see figure).
我们首先启动回复者,然后启动请求者。然后,发生了一件非常奇怪的事情。请求者控制台窗口声称在回复者确认收到请求之前已收到响应。控制台输出有延迟吗?由于缺乏任何好的想法,我们决定关闭回复者,并重新运行请求者。奇怪的是,我们仍然收到了对我们请求的回复!魔法?不,这只是持久消息传递的副作用。ReplyQueue上存在多余的消息,很可能是由之前的故障引起的。每次我们启动请求程序时,它都会在 RequestQueue 上放置一条新消息,然后立即检索ReplyQueue上的无关回复消息。我们从来没有注意到这条消息并不是对请求者刚刚提出的请求的回复!一旦回复者收到新的请求消息,它就会在ReplyQueue上放置一条新的回复消息,以便在下一次测试期间重复“魔法”。即使是在最简单的场景中,持久的异步消息传递也会对您进行欺骗,这可能令人惊讶(或令人惊讶地令人沮丧)!
We started the replier first, then the requestor. Then, a very odd thing happened. The requestor console window claimed to have gotten a response before the replier ever acknowledged receiving a request. A delay in the console output? Lacking any great ideas, we decided to shut the replier down, and we reran the requestor. Oddly enough, we still received a response to our request! Magic? No, just a side effect of persistent messaging. A superfluous message was present on the ReplyQueue, most likely caused by an earlier failure. Every time we started the requestor, it placed a new message on the RequestQueue and then immediately retrieved the extraneous reply message that was sitting on the ReplyQueue. We never noticed that this message was not the reply to the request the requestor had just made! Once the replier received the new request message, it placed a new reply message on the ReplyQueue so that the "magic" repeated during the next test. It can be amazing (or amazingly frustrating) how persistent, asynchronous messaging can play tricks on you in even the most simple scenarios!
|
如何防止通道上的剩余消息干扰测试或正在运行的系统? How can you keep leftover messages on a channel from disturbing tests or running systems? |
消息渠道即使接收组件不可用,也能可靠地传递消息。为此,通道必须沿途保留消息。这一有用的功能可能会在测试期间或其中一个组件行为不当(并且不使用事务性消息消费和生产)时导致混乱的情况。正如前面所描述的,我们很快就会在通道上遇到无关的消息。这些消息使得在待处理消息被消耗之前无法将测试数据传递到系统中。如果待处理的消息是价值几百万美元的订单,这是一件好事。如果我们正在测试或调试一个系统,并且有一个充满查询消息或回复消息的通道,这可能会给我们带来相当大的麻烦。
Message Channels are designed to deliver messages reliably even if the receiving component is unavailable. In order to do so, the channel has to persist messages along the way. This useful feature can cause confusing situations during testing or if one of the components misbehaves (and does not use transactional message consumption and production). We can quickly end up with extraneous messages stuck on channels, as previously described. These messages make it impossible to pass test data into the system until the pending messages have been consumed. If the pending messages are orders worth a few million dollars, this is a good thing. If we are testing or debugging a system and have a channel full of query messages or reply messages, it can cause us a fair amount of headache.
在我们的简单示例中,如果我们使用Correlation Identifier ,我们的一些调试痛苦可能会得到缓解。使用该标识符,请求者将认识到传入消息实际上不是对其刚刚发送的请求的响应。然后,它可以丢弃旧的回复消息或将其路由到无效消息通道,这将有效地消除“卡住”的消息。在其他情况下,检测重复或不需要的消息并不容易。例如,如果特定邮件格式错误并导致邮件收件人失败,则收件人无法重新启动,直到错误邮件被删除,因为它会立即再次失败。当然,此示例需要纠正接收者中的缺陷(格式错误的消息不应导致组件故障),但删除该消息可以使系统快速启动并运行,直到缺陷得到纠正。
In our simple example, some of our debugging pain could have been eased if we had used a Correlation Identifier. Using the identifier, the requestor would have recognized that the incoming message is actually not the response to the request it just sent. It could then discard the old reply message or route it to an Invalid Message Channel, which would effectively remove the "stuck" message. In other scenarios, it is not as easy to detect duplicate or unwanted messages. For example, if a specific message is malformed and causes the message recipient to fail, the recipient cannot restart until the bad message is removed, because it would just fail right away again. Of course, this example requires the defect in the recipient to be corrected (no malformed message should cause a component failure), but removing the message can get the system up and running quickly until the defect is corrected.
避免通道中剩余消息的另一种方法是使用临时通道(例如,JMS为此提供了 createTemporaryQueue方法)。这些通道适用于请求-回复应用程序,一旦应用程序关闭与消息传递系统的连接,它们就会丢失所有消息。但同样,这种方法仅限于简单的请求-答复示例,并且不能防止其他消息留在需要持久化的其他通道上。
Another way to avoid leftover messages in channels is to use temporary channels (e.g., JMS provides the method createTemporaryQueue for this purpose). These channels are intended for request-reply applications and lose all messages once the application closes its connection to the messaging system. But again, this approach is limited to a simple request-reply example and does not protect against other messages being left over on other channels that need to be persistent.
人们可能会认为事务管理可以消除额外的消息场景,因为消息消费、消息处理和消息发布都包含在单个事务中。因此,如果组件在处理消息的过程中中止,则该消息不会被视为已消耗。同样,在组件发出发送消息的最终提交信号之前,不会发布回复消息。但我们需要记住,事务并不能保护我们免受编程错误的影响。在我们简单的请求-回复示例中,程序员错误可能导致请求者无法从 ReplyQueue 读取响应渠道。因此,尽管存在潜在的事务性,一条消息仍滞留在该通道上,从而导致前面描述的症状。
It may be tempting to assume that transaction management can eliminate the extra message scenario because message consumption, message processing, and message publication are covered in a single transaction. So, if a component aborts in the middle of processing a message, the message would not be considered consumed. Likewise, a reply message would not be published until the component signals the final commit to send the message. We need to keep in mind, though, that transactions do not protect us against programming errors. In our simple request-reply example, a programmer error may have caused the requestor to not read a response from the ReplyQueue channel. As a result, despite potential transactionality, a message is stuck on that channel, causing the symptoms described earlier.
|
使用通道清除器从通道中删除不需要的消息。 Use a Channel Purger to remove unwanted messages from a channel. |
基本的通道清除器只是从通道中删除所有消息。对于我们想要将系统重置为一致状态的测试场景来说,这可能足够了。如果我们正在调试生产系统,我们可能需要根据特定条件(例如消息 ID 或特定消息字段的值)删除单个消息或一组消息。
A basic Channel Purger simply removes all the messages from a channel. This may be sufficient for test scenarios where we want to reset the system into a consistent state. If we are debugging a production system, we may need to remove an individual message or a set of messages based on specific criteria, such as the message ID or the values of specific message fields.
在许多情况下,Channel Purger可以简单地将消息从通道中删除并丢弃。在其他情况下,我们可能需要Channel Purger来存储删除的消息以供以后检查或重放。如果通道上的消息导致系统故障,这非常有用,因此我们需要删除它们才能继续操作。但是,一旦问题得到纠正,我们希望重新注入消息,以便系统不会丢失消息的内容。这还可能包括在重新注入消息之前编辑消息内容的要求。这种类型的函数结合了Message Store和Channel Purger的一些功能。
In many cases, it is all right for the Channel Purger to simply delete the message from the channel and discard it. In other cases, we may need the Channel Purger to store the removed messages for later inspection or replay. This is useful if the messages on a channel cause a system to malfunction, so we need to remove them to continue operation. However, once the problems are corrected, we want to re-inject the message(s) so that the system does not lose the contents of the message. This may also include the requirement to edit message contents before re-injecting the message. This type of function combines some of the features of a Message Store and a Channel Purger.
本章使用一个更详细的示例来演示如何使用本节中介绍的系统管理模式来监视和控制消息传递解决方案。该示例基于第 9 章“插曲:组合消息传递”中贷款经纪人示例的 C# 和 MSMQ 实现(请参阅“使用 MSMQ 进行异步实现”)")。我们增强而不是修改原始示例解决方案,因此您查看原始示例的所有代码并不重要。与原始实现一样,此示例的目的不是解释特定于 MSMQ 的使用API 而是为了使用面向队列的消息传递系统来说明本书中模式的实现。当使用 JMS 队列或 IBM WebSphere MQ 在 Java 中实现时,解决方案的结构看起来非常相似。因为我们主要关注设计决策和权衡,即使您不是 C# 或 MSMQ 开发人员,本章也应该对您有价值。
This chapter uses a more elaborate example to demonstrate how the system management patterns introduced in this section can be used to monitor and control a messaging solution. The example builds upon the C# and MSMQ implementation of the loan broker example from Chapter 9, "Interlude: Composed Messaging" (see "Asynchronous Implementation with MSMQ"). We augment rather than modify the original example solution, so it is not critical that you reviewed all the code of the original example. As with the original implementation, the intent of this example is not to explain the use of MSMQ-specific APIs but rather to illustrate the implementation of the patterns in this book using a queue-oriented messaging system. The structure of the solution would look very similar when implemented in Java using JMS queues or IBM WebSphere MQ. Because we focus mostly on the design decisions and trade-offs, this chapter should be valuable to you even if you are not a C# or MSMQ developer.
贷款经纪人实施由以下四个关键组件组成(见图):
The loan broker implementation consists of the following four key components (see figure):
客户(或测试客户)提出贷款报价请求。
The customer (or test client) makes requests for loan quotes.
贷款经纪人充当中央流程管理者,协调征信局和银行之间的沟通。
The loan broker acts as the central process manager and coordinates the communication between the credit bureau and the banks.
信用局向贷款经纪人提供服务,计算客户的信用评分。
The credit bureau provides a service to the loan broker, computing customers' credit scores.
每家银行都会收到贷款经纪人的报价请求,并根据贷款参数提交利率报价。
Each bank receives a quote request from the loan broker and submits an interest rate quote according to the loan parameters.
在大多数集成场景中,我们无法访问应用程序内部,只能从外部监视和管理组件。为了使这个示例尽可能真实,我们将每个现有组件视为黑匣子。考虑到这一限制,我们希望管理解决方案满足以下要求:
In most integration scenarios, we do not have access to the application internals but are limited to monitoring and managing components from the outside. To make this example as realistic as possible, we treat each of the existing components as a black box. Keeping this constraint in mind, we want the management solution to meet the following requirements:
管理控制台: 我们需要一个单一前端来显示所有组件的运行状况,并允许我们在出现问题时采取补偿措施。
Management Console: We want a single front end that displays the health of all components and allows us to take compensating actions if something goes wrong.
贷款经纪人服务质量: 在最初的解决方案中,我们开发了一个测试客户端,用于监控贷款经纪人在报价请求和响应之间的响应时间。在真实的生产场景中,客户不会为我们执行此功能(但是他们可能会抱怨系统太慢)。因此,我们希望管理解决方案能够捕获此信息并将其转发到管理控制台。
Loan Broker Quality of Service: In the original solution, we developed a test client that monitors the loan broker's response times between quote request and response. In a real production scenario, customers will not perform this function for us (they may, however, complain that the system is too slow). Therefore, we want the management solution to capture this information and relay it to the management console.
验证信用局操作: 信用局是第三方提供的外部服务。我们希望通过定期发送测试消息来确保该服务的正确运行。
Verify the Credit Bureau Operation: The credit bureau is an external service provided by a third party. We want to ensure the correct operation of this service by periodically sending test messages.
信用局故障转移: 如果信用局发生故障,我们希望暂时将信用请求消息重定向到另一个服务提供商。
Credit Bureau Failover: If the credit bureau malfunctions, we want to temporarily redirect the credit request messages to another service provider.
为了评估整个解决方案的运行状况,我们需要能够将多个组件的指标收集到单个点(管理控制台)。该控制台还必须能够控制消息流和组件参数,以便我们可以通过重新路由消息或更改组件行为来解决中断问题。
To assess the health of the overall solution, we need to be able to collect metrics from multiple components to a single point, the management console. This console also has to be able to control message flow and component parameters so that we can address outages by rerouting messages or changing component behavior.
管理控制台通过消息传递与各个组件进行通信。它使用单独的控制总线,仅包含与系统管理相关的消息,而不包含与应用程序数据相关的消息。
The management console communicates with the individual components via messaging. It uses a separate Control Bus that only contains messages related to system management and not to application data.
因为这是一本关于企业集成而不是用户界面设计的书,所以我们使管理控制台非常非常简单。许多供应商提供实时数据显示,您甚至可以使用 Visual Basic 和 Microsoft Office 组件(例如 Excel)实现视觉奇迹。此外,许多操作系统和编程平台都提供自己的工具框架,例如 Java/JMX(Java 管理扩展)或 Microsoft 的 WMI(Windows 管理工具)。我们手动推出我们的解决方案,以减少其对特定供应商 API 的依赖,并演示监控解决方案的内部工作原理。我们在实现具体的管理功能时,会搭建这个控制台。
Because this is a book on enterprise integration and not on user interface design, we keep the management console very, very simple. Many vendors offer real-time data displays, or you can even achieve visual miracles with Visual Basic and Microsoft Office components such as Excel. Also, many operating systems and programming platforms, offer their own instrumentation frameworks, such as Java/JMX (Java Management Extensions) or Microsoft's WMI (Windows Management Instrumentation). We hand-roll our solution to make it less dependent on a specific vendor's API and to demonstrate the inner workings of a monitoring solution. We will build up this console as we implement the specific management functions.
管理解决方案的首要要求是衡量贷款经纪人向客户提供的服务质量。对于这种类型的监控,我们对各个消息的业务内容(即提供给客户端的利率)不感兴趣,而只对请求消息和回复消息之间经过的时间感兴趣。跟踪这两个消息之间的时间的棘手部分是客户端可以通过 Return Address 指定回复消息的通道,因此我们无法在固定通道上侦听回复。幸运的是,智能代理模式为我们解决了这个困境。智能代理拦截请求消息,存储返回地址由客户端提供,并替换为固定的回复通道地址。因此,服务(在我们的例子中是贷款经纪人)将所有回复消息发送到一个通道。智能代理侦听该通道并将传入的回复消息与存储的请求消息相关联。然后它将回复消息转发到客户端指定的原始返回地址(见图)。
The first requirement for the management solution is to measure the quality of service that the loan broker provides to its clients. For this type of monitoring, we are not interested in the business content of the individual messagesthat is, the interest rate offered to the clientbut only in the time elapsed between the request message and the reply message. The tricky part in tracking the time between these two messages is that the client can specify the channel for the reply message via a Return Address, so we cannot listen on a fixed channel for the reply. Luckily, the Smart Proxy pattern solves this dilemma for us. A Smart Proxy intercepts a request message, stores the Return Address supplied by the client, and replaces it with a fixed reply channel address. As a result, the service (the loan broker in our case) sends all reply messages to one channel. The Smart Proxy listens to this channel and correlates incoming reply messages to stored request messages. It then forwards the reply message to the original Return Address specified by the client (see figure).
使用智能代理检测贷款经纪人
Instrumenting the Loan Broker with a Smart Proxy
为了利用智能代理功能,我们在客户和贷款经纪人之间“插入”智能代理(见上图)。此插入对客户端来说是透明的,因为智能代理侦听贷款经纪人最初侦听的同一通道 ( loanRequestQueue) 。现在,我们使用新参数启动贷款经纪人,以便它侦听brokerRequestQueue而不是loanRequestQueue通道。智能代理指示贷款经纪人将所有回复消息发送到BrokerReplyQueue通道,并从该通道将消息转发回正确的位置。最初由客户指定的返回地址。
In order to take advantage of the Smart Proxy functionality, we "insert" the Smart Proxy between the client and the loan broker (see figure above). This insertion is transparent to the client because the Smart Proxy listens on the same channel that the loan broker originally listened on (loanRequestQueue). We now start the loan broker with new parameters so that it listens on the brokerRequestQueue instead of the loanRequestQueue channel. The Smart Proxy instructs the loan broker to send all reply messages to the brokerReplyQueue channel from where it forwards the messages back to the correct Return Address originally specified by the client.
我们希望使用智能代理来衡量贷款请求的响应时间以及贷款经纪人在任一时间处理的请求数量。智能代理可以通过捕获接收请求消息的时间来测量请求和回复消息之间经过的时间。当智能代理收到关联的回复消息时,它会从当前时间中减去请求时间,以计算请求和回复之间经过的时间。智能代理可以通过计算有多少未完成的请求消息(即尚未收到回复消息的请求消息)来估计贷款经纪人一次管理的活动请求数量。这智能代理无法区分在brokerRequestQueue 上排队的消息和贷款经纪人开始处理的消息,因此该指标等于两者的总和。每当我们收到请求消息或回复消息时,我们都可以更新未完成的请求消息的数量。
We want to use the Smart Proxy to measure both the response time for loan requests and the number of requests being processed by the loan broker at any one time. The Smart Proxy can measure the time elapsed between request and reply messages by capturing the time that the request message was received. When it receives the associated reply message, the Smart Proxy subtracts the request time from the current time to compute the time elapsed between request and reply. The Smart Proxy can estimate how many active requests the loan broker is managing at one time by counting how many outstanding request messages there are (i.e., request messages that have not yet received reply messages). The Smart Proxy cannot distinguish between messages queued up on the brokerRequestQueue and messages that the loan broker started processing, so this metric equals the sum of both. We can update the number of outstanding request messages whenever we receive a request message or a reply message.
智能代理通过controlBusQueue通道将指标信息传递到管理控制台进行监控和分析。我们可以发送每条消息的统计信息,但如果我们处理大量消息,这会使我们的网络变得混乱。在消息流中插入智能代理已经使发送的消息数量增加了一倍(两个请求和回复消息,而不是各一个),因此我们希望避免为每个请求消息发送另一个控制消息。相反,我们使用计时器,以便智能代理向控制总线发送指标消息以预定义的时间间隔,例如每 5 秒。度量消息可以包含摘要度量(例如,最大、最小和平均响应时间)或该时间间隔期间通过的所有消息的详细信息。为了保持指标消息较小且管理控制台简单,我们决定仅将摘要指标传递到控制台。
The Smart Proxy passes the metrics information to the management console for monitoring and analysis via the controlBusQueue channel. We could send the statistics for every single message, but that would clutter our network if we deal with high message volumes. Inserting a Smart Proxy into the message flow already doubles the number of messages sent (two request and reply messages instead of one each), so we want to avoid sending another control message for each request message. Instead, we use a timer so that the Smart Proxy sends a metrics message to the Control Bus in predefined intervals, for example, every 5 seconds. The metrics message can contain either summary metrics (e.g., the maximum, minimum, and average response time) or the detailed information for all messages that passed through during the interval. In order to keep the metrics messages small and the management console simple, we decide to just pass the summary metrics to the console.
为了实现贷款经纪人智能代理,我们重用智能代理模式中引入的SmartProxy基类。我们对SmartProxyBase 、 SmartProxyRequestConsumer和SmartProxyReplyConsumer类进行子类化(请参阅类图)。请参阅智能代理模式以获取这些类的源代码。
For the implementation of the loan broker Smart Proxy, we reuse the SmartProxy base classes introduced in the Smart Proxy pattern. We subclass the SmartProxyBase, SmartProxyRequestConsumer, and SmartProxyReplyConsumer classes (see class diagram). Please refer to the Smart Proxy pattern for the source code for these classes.
贷款经纪人智能代理类图
Loan Broker Smart Proxy Class Diagram
就像原来的SmartProxy 一样,新的LoanBrokerProxy包含两个独立的消息使用者,一个用于来自客户端的传入请求消息 ( LoanBrokerProxyRequestConsumer ),另一个用于来自贷款经纪人的传入回复消息 ( LoanBrokerProxyReplyConsumer ) 。这两个使用者类都继承自各自的基类(SmartProxyRequestConsumer 和SmartProxyReplyConsumer ),并添加了AnalyzeMessage方法的新实现。
Just like the original SmartProxy, the new LoanBrokerProxy contains two separate message consumers, one for incoming request messages from the client (LoanBrokerProxyRequestConsumer) and one for incoming reply messages from the loan broker (LoanBrokerProxyReplyConsumer). Both consumer classes inherit from their respective base classes (SmartProxyRequestConsumer and SmartProxyReplyConsumer) and add a new implementation of the AnalyzeMessage method.
让我们看一下LoanBrokerProxy类的实现。该构造函数采用与SmartProxyBase 基类相同的参数,以及对控制和以秒为单位的报告间隔。
Let's have a look at the implementation of the LoanBrokerProxy class. The constructor takes the same parameters as the SmartProxyBase base class, plus a reference to a Control Bus queue and the reporting interval in seconds.
该类维护两个带有指标、 PerformanceStats和queueStats的ArrayList。 PerformanceStats收集响应-回复时间间隔(以秒为单位)的数据点,而queueStats收集未完成请求消息数量的数据点(这些消息在brokerRequestQueue 中排队或由贷款经纪人处理)。当预编程的计时器触发时, OnTimerEvent方法会拍摄两个集合中数据的快照。我们必须使用此快照副本对数据进行进一步的分析,因为消息使用者在收到消息时会继续添加新的数据点。
The class maintains two ArrayLists with metrics, performanceStats, and queueStats. performanceStats collects data points of response-reply time intervals in seconds, while queueStats collects data points of the number of outstanding request messages (those either queued up in the brokerRequestQueue or in process by the loan broker). When the preprogrammed timer triggers, the method OnTimerEvent takes a snapshot of the data in both collections. We have to perform any further analysis of the data with this snapshot copy because the message consumers continue to add new data points as messages are received.
公共类 LoanBrokerProxy :SmartProxyBase
{
受保护的消息队列控制总线;
受保护的 ArrayList 性能统计;
受保护的 ArrayList 队列统计信息;
受保护的 int 区间;
受保护的定时器定时器;
公共LoanBrokerProxy(消息队列输入队列,消息队列服务请求队列,
MessageQueue服务ReplyQueue、MessageQueue控制总线、
整数间隔):
基(输入队列,服务请求队列,服务回复队列)
{
messageData = Hashtable.Synchronized(new Hashtable());
queueStats = ArrayList.Synchronized(new ArrayList());
PerformanceStats = ArrayList.Synchronized(new ArrayList());
this.controlBus = controlBus;
this.interval = 间隔;
requestConsumer = new LoanBrokerProxyRequestConsumer(inputQueue,
服务请求队列、服务回复队列、消息数据、队列统计);
回复消费者 = 新 LoanBrokerProxyReplyConsumer(serviceReplyQueue,
消息数据、队列统计、性能统计);
}
公共重写 void Process()
{
基.进程();
TimerCallback timeDelegate = new TimerCallback(OnTimerEvent);
计时器=新计时器(timerDelegate,空,间隔* 1000,间隔* 1000);
}
protected void OnTimerEvent(对象状态)
{
ArrayList 当前队列统计信息;
ArrayList 当前性能统计;
锁(队列统计)
{
currentQueueStats = (ArrayList)(queueStats.Clone());
队列统计.Clear();
}
锁定(性能统计)
{
currentPerformanceStats = (ArrayList)(performanceStats.Clone());
性能统计.Clear();
}
SummaryStats 摘要 = new SummaryStats(currentQueueStats,
当前性能统计);
if (controlBus!= null)
controlBus.Send(摘要);
}
}
public class LoanBrokerProxy : SmartProxyBase
{
protected MessageQueue controlBus;
protected ArrayList performanceStats;
protected ArrayList queueStats;
protected int interval;
protected Timer timer;
public LoanBrokerProxy(MessageQueue inputQueue, MessageQueue serviceRequestQueue,
MessageQueue serviceReplyQueue, MessageQueue controlBus,
int interval) :
base (inputQueue, serviceRequestQueue, serviceReplyQueue)
{
messageData = Hashtable.Synchronized(new Hashtable());
queueStats = ArrayList.Synchronized(new ArrayList());
performanceStats = ArrayList.Synchronized(new ArrayList());
this.controlBus = controlBus;
this.interval = interval;
requestConsumer = new LoanBrokerProxyRequestConsumer(inputQueue,
serviceRequestQueue, serviceReplyQueue, messageData, queueStats);
replyConsumer = new LoanBrokerProxyReplyConsumer(serviceReplyQueue,
messageData, queueStats, performanceStats);
}
public override void Process()
{
base.Process();
TimerCallback timerDelegate = new TimerCallback(OnTimerEvent);
timer = new Timer(timerDelegate, null, interval*1000, interval*1000);
}
protected void OnTimerEvent(Object state)
{
ArrayList currentQueueStats;
ArrayList currentPerformanceStats;
lock (queueStats)
{
currentQueueStats = (ArrayList)(queueStats.Clone());
queueStats.Clear();
}
lock (performanceStats)
{
currentPerformanceStats = (ArrayList)(performanceStats.Clone());
performanceStats.Clear();
}
SummaryStats summary = new SummaryStats(currentQueueStats,
currentPerformanceStats);
if (controlBus != null)
controlBus.Send(summary);
}
}
LoanBrokerProxy使用SummaryStats结构将各个数据点压缩为最大值、最小值和平均值,然后将摘要数据发送到控制总线。 我们可以通过更新每条传入消息的摘要统计信息来提高评估效率,这样我们就只需存储摘要数据而不是每个数据点。但是,推迟计算允许我们更改要发布到控制总线的详细信息量。
The LoanBrokerProxy uses the SummaryStats structure to condense the individual data points into maximum, minimum, and average values and then sends the summary data to the Control Bus. We could make the evaluation more efficient by updating the summary statistics with each incoming message so that we have to store only the summary data and not each data point. However, deferring the computation allows us to change the amount of detail we want to publish to the Control Bus.
LoanBrokerProxyRequestConsumer类处理传入的请求消息。基类SmartProxyRequestConsumer负责将相关消息数据存储在messageData 哈希表中。同样,SmartProxyReplyConsumer 的基本实现每当收到回复消息时都会从该哈希表中删除数据。因此,我们可以从messageData哈希表的大小得出当前未完成的请求消息的数量。LoanBrokerProxyRequestConsumer维护对存储在LoanBrokerProxyqueueStats集合的引用以便它可以将新数据点添加到该集合中。
The LoanBrokerProxyRequestConsumer class handles incoming request messages. The base class SmartProxyRequestConsumer takes care of storing relevant message data in the messageData hash table. Likewise, the base implementation of the SmartProxyReplyConsumer removes data from that hash table whenever it receives a reply message. As a result, we can derive the current number of outstanding request messages from the size of the messageData hash table. The LoanBrokerProxyRequestConsumer maintains a reference to the queueStats collection stored inside the LoanBrokerProxy so that it can add the new data point to this collection.
公共类 LoanBrokerProxyRequestConsumer :SmartProxyRequestConsumer
{
ArrayList队列统计;
公共LoanBrokerProxyRequestConsumer(消息队列请求队列,
消息队列服务请求队列,
消息队列服务回复队列,
哈希表消息数据,
ArrayList队列统计):
基础(请求队列,服务请求队列,服务回复队列,消息数据)
{
this.queueStats = queueStats;
}
protected override void ProcessMessage(消息 requestMsg)
{
基.ProcessMessage(requestMsg);
queueStats.Add(messageData.Count);
}
}
public class LoanBrokerProxyRequestConsumer : SmartProxyRequestConsumer
{
ArrayList queueStats;
public LoanBrokerProxyRequestConsumer(MessageQueue requestQueue,
MessageQueue serviceRequestQueue,
MessageQueue serviceReplyQueue,
Hashtable messageData,
ArrayList queueStats) :
base(requestQueue, serviceRequestQueue, serviceReplyQueue, messageData)
{
this.queueStats = queueStats;
}
protected override void ProcessMessage(Message requestMsg)
{
base.ProcessMessage(requestMsg);
queueStats.Add(messageData.Count);
}
}
当回复消息到达时, LoanBrokerProxyReplyConsumer会收集两个所需的指标。首先,它计算发送请求消息和接收回复消息之间所花费的时间,并将该指标添加到PerformanceStats集合中。其次,它捕获剩余的未完成请求数(再次使用messageData哈希表的大小)并将该数字添加到queueStats集合中。
The LoanBrokerProxyReplyConsumer collects the two required metrics when a reply message arrives. First, it computes the time it took between sending the request message and receiving the reply message and adds that metric to the performanceStats collection. Second, it captures the remaining number of outstanding requests (again using the size of the messageData hash table) and adds that number to the queueStats collection.
公共类 LoanBrokerProxyReplyConsumer :SmartProxyReplyConsumer
{
ArrayList队列统计;
ArrayList 性能统计;
公共LoanBrokerProxyReplyConsumer(消息队列回复队列,
哈希表消息数据,
ArrayList队列统计,
ArrayList 性能统计):
基础(回复队列,消息数据)
{
this.queueStats = queueStats;
this.performanceStats = PerformanceStats;
}
protected override void AnalyzeMessage(MessageData data, 消息回复消息)
{
TimeSpan 持续时间 = DateTime.Now - data.SentTime;
PerformanceStats.Add(duration.TotalSeconds);
queueStats.Add(messageData.Count);
}
}
public class LoanBrokerProxyReplyConsumer : SmartProxyReplyConsumer
{
ArrayList queueStats;
ArrayList performanceStats;
public LoanBrokerProxyReplyConsumer(MessageQueue replyQueue,
Hashtable messageData,
ArrayList queueStats,
ArrayList performanceStats) :
base(replyQueue, messageData)
{
this.queueStats = queueStats;
this.performanceStats = performanceStats;
}
protected override void AnalyzeMessage(MessageData data, Message replyMessage)
{
TimeSpan duration = DateTime.Now - data.SentTime;
performanceStats.Add(duration.TotalSeconds);
queueStats.Add(messageData.Count);
}
}
SummaryStats结构根据捕获的数据计算最大值、最小值和平均值。它可以通过从队列大小数据点的数量(为请求和回复消息收集)减去性能数据点的数量(仅为回复消息收集)来得出处理的请求消息的数量。这个结构的实现非常简单,因此我们决定不用代码填充整个页面。
The SummaryStats structure computes maximum, minimum, and average values based on the captured data. It can derive the number of request messages processed by subtracting the number of performance data points (collected only for reply messages) from the number of queue size data points (collected for request and reply messages). The implementation of this structure is quite trivial, so we decided not to fill a whole page with the code.
一旦我们将新的贷款经纪人代理插入消息流中,我们就可以开始收集性能指标。为了收集一些示例数据,我们配置了两个测试客户端,每个客户端发出 50 个贷款报价请求。代理收集了以下结果(我们使用简单的 XSL 转换来渲染以 XML 格式发布到 controlBusQueue 的指标数据的 HTML 表) :
Once we insert the new loan broker proxy into the message stream, we can start collecting performance metrics. To collect some example data, we configured two test clients to make 50 loan quote requests each. The proxy collected the following results (we used a simple XSL transform to render an HTML table off the metric data published in XML format to the controlBusQueue):
时间戳 Time Stamp | 请求数量 Number of Requests | 回复数 Number of Replies | 最短处理时间 Minimum Processing Time | 平均处理时间 Average Processing Time | 最大处理时间 Maximum Processing Time | 最小队列大小 Minimum Queue Size | 平均队列大小 Average Queue Size | 最大队列大小 Maximum Queue Size |
|---|---|---|---|---|---|---|---|---|
14:11:02.9644424 14:11:02.9644424 | 0 0 | 0 0 | 0.00 0.00 | 0.00 0.00 | 0.00 0.00 | 0 0 | 0 0 | 0 0 |
14:11:07.9718424 14:11:07.9718424 | 89 89 | 7 7 | 0.78 0.78 | 2.54 2.54 | 3.93 3.93 | 1 1 | 42 42 | 82 82 |
14:11:12.9792424 14:11:12.9792424 | 11 11 | 9 9 | 4.31 4.31 | 6.43 6.43 | 8.69 8.69 | 83 83 | 87 87 | 91 91 |
14:11:17.9866424 14:11:17.9866424 | 0 0 | 8 8 | 9.39 9.39 | 10.83 10.83 | 12.82 12.82 | 77 77 | 80 80 | 84 84 |
14:11:22.9940424 14:11:22.9940424 | 0 0 | 8 8 | 13.80 13.80 | 15.75 15.75 | 17.48 17.48 | 69 69 | 72 72 | 76 76 |
14:11:28.0014424 14:11:28.0014424 | 0 0 | 7 7 | 18.37 18.37 | 20.19 20.19 | 22.18 22.18 | 62 62 | 65 65 | 68 68 |
14:11:33.0088424 14:11:33.0088424 | 0 0 | 6 6 | 22.90 22.90 | 24.83 24.83 | 26.94 26.94 | 56 56 | 58 58 | 61 61 |
14:11:38.0162424 14:11:38.0162424 | 0 0 | 10 10 | 27.74 27.74 | 29.53 29.53 | 31.62 31.62 | 46 46 | 50 50 | 55 55 |
14:11:43.0236424 14:11:43.0236424 | 0 0 | 9 9 | 31.87 31.87 | 34.47 34.47 | 36.30 36.30 | 37 37 | 41 41 | 45 45 |
14:11:48.0310424 14:11:48.0310424 | 0 0 | 7 7 | 36.87 36.87 | 39.06 39.06 | 40.98 40.98 | 30 30 | 33 33 | 36 36 |
14:11:53.0384424 14:11:53.0384424 | 0 0 | 9 9 | 41.75 41.75 | 43.82 43.82 | 45.14 45.14 | 21 21 | 25 25 | 29 29 |
14:11:58.0458424 14:11:58.0458424 | 0 0 | 8 8 | 45.92 45.92 | 47.67 47.67 | 49.67 49.67 | 13 13 | 16 16 | 20 20 |
14:12:03.0532424 14:12:03.0532424 | 0 0 | 8 8 | 50.86 50.86 | 52.58 52.58 | 54.59 54.59 | 5 5 | 8 8 | 12 12 |
14:12:08.0606424 14:12:08.0606424 | 0 0 | 4 4 | 55.41 55.41 | 55.96 55.96 | 56.69 56.69 | 1 1 | 2 2 | 4 4 |
14:12:13.0680424 14:12:13.0680424 | 0 0 | 0 0 | 0.00 0.00 | 0.00 0.00 | 0.00 0.00 | 0 0 | 0 0 | 0 0 |
加载到 Excel 图表中,队列大小数据如下所示:
Loaded into an Excel chart, the queue size data looks like this:
贷款经纪人智能代理统计数据
Loan Broker Smart Proxy Statistics
我们可以看到,这两个测试客户几乎淹没了贷款经纪人,峰值约为 90 个待处理请求。然后,贷款经纪人以每秒约 2 条请求消息的稳定速率处理请求。由于排队请求数量较多,响应时间很差,峰值接近 1 分钟。好消息是贷款经纪人可以优雅地处理大量突发请求,而坏消息是响应时间非常长。为了缩短响应时间,我们可以执行多个贷款经纪人实例或多个信用局实例(事实证明,信用局服务是第 9 章“插曲:组合消息传递”中描述的原始实现中的瓶颈)。
We can see that the two test clients pretty much flooded the loan broker, peaking at about 90 pending requests. The loan broker then processes the requests at a stable rate of about 2 request messages per second. Due to the large number of queued-up requests, the response times are pretty poor, peaking at almost 1 minute. The good news is that the loan broker handles a large number of sudden requests gracefully, while the bad news is that the response times are very long. In order to improve response times, we could execute multiple loan broker instances or multiple credit bureau instances (the credit bureau service turned out to be a bottleneck in the original implementation described in Chapter 9, "Interlude: Composed Messaging").
管理解决方案的第二个要求是监控外部信用局服务的正确运行。贷款经纪人访问此服务以获得请求贷款报价的客户的信用评分,因为银行需要此信息来提供准确的报价。
The second requirement for the management solution is to monitor the correct operation of the external credit bureau service. The loan broker accesses this service to obtain credit scores for customers requesting a loan quote because the banks require this information to provide an accurate quote.
为了验证外部信用局服务的正确运行,我们决定定期向该服务发送测试消息。由于信用局服务支持返回地址,因此可以轻松注入测试消息,而不会干扰现有的消息流。我们只是为测试消息提供专用的回复通道,这样就不需要单独的测试消息分隔符(见图)。
In order to verify the correct operation of the external credit bureau service, we decide to send periodic Test Messages to the service. Because the credit bureau service supports a Return Address, it is easy to inject a Test Message without disturbing the existing message flow. We simply provide a dedicated reply channel for test messages which avoids the need for a separate test message separator (see figure).
监控信用局服务
Monitoring the Credit Bureau Service
为了验证征信机构服务的正确运行,我们需要一个测试数据生成器和一个测试数据验证器。测试数据生成器创建要发送到被测服务的测试数据。信用局测试消息非常简单;唯一需要填写的字段是社会安全号码 (SSN)。在我们的测试中,我们使用一个特殊的、固定的 SSN 来识别虚构的人。这使我们能够通过将结果数据与预先建立的结果进行比较来验证结果数据。这样我们不仅可以检查是否收到回复消息,还可以验证消息内容是否正确。在第 9 章介绍的简单示例中,“插曲:组合消息”,信用局服务被编程为返回随机结果,无论传入的 SSN 是什么。因此,我们的测试数据验证程序不会检查特定的结果值,而是验证结果是否在允许的范围内(例如,信用评分为 300 到 900)。如果结果超出允许的范围(例如,由于计算错误将分数设置为零),测试数据验证器会通过消息通知管理控制台。
In order to verify the correct operation of the credit bureau service, we need a test data generator and a test data verifier. The test data generator creates test data to be sent to the service under test. A credit bureau test message is very simple; the only field that is required is a social security number (SSN). For our tests we use a special, fixed SSN that identifies a fictitious person. This allows us to verify the result data by comparing it to preestablished results. This way we can not only check whether we receive a reply message but also verify that the content of the message is correct. In our simple example introduced in Chapter 9, "Interlude: Composed Messaging," the credit bureau service is programmed to return random results regardless of the incoming SSN. As a result, our test data verifier does not check for specific result values but instead verifies whether the results are within the allowed range (e.g., 300 to 900 for a credit score). If the results fall outside the allowed range (for example, because a computational error set the score to zero), the test data verifier notifies the management console with a message.
测试数据验证器还检查外部服务的响应时间。如果我们在预设的时间间隔内没有收到回复消息,我们也会向管理控制台发出警报。为了最大限度地减少网络带宽,测试数据验证程序仅在响应延迟或格式错误时通知控制台,而不是在服务正常运行时通知控制台。当监视器在检测到错误后从服务收到正确的回复消息时,会发生此规则的唯一例外。在这种情况下,监控器会向管理控制台发送“服务正常”消息,表明信用局再次正常工作。最后,在启动过程中,监视器会向控制台发送一条消息以宣布其存在。
The test data verifier also checks the response time of the external service. If we do not receive a reply message within a preset time interval, we also alert the management console. To minimize network bandwidth, the test data verifier notifies the console only if the response is delayed or malformed, not when the service is operating correctly. The only exception to this rule occurs when the monitor receives a correct reply message from the service subsequent to detecting an error. In that case, the monitor sends a "service OK" message to the management console to indicate that the credit bureau is working correctly again. Finally, during startup, the monitor sends a message to the console to announce its existence. This message allows the console to "discover" all active monitors so it can display the status for each.
监视器实现使用两个单独的计时器:一个用于以指定的时间间隔发送测试消息,另一个用于在指定的超时期限内未到达响应时标记异常(见图)。发送定时器确定最后接收消息或最后超时事件与发送下一条测试消息之间的时间间隔。每当监视器发送请求消息时,超时计时器就会启动。如果回复消息在指定的超时间隔内到达,则超时计时器将重置并重新启动下一条请求消息。如果监视器在指定的时间间隔内未收到回复消息,则超时定时器将触发,监视器将向控制总线发送错误消息。然后,它启动一个新的发送计时器,以在发送间隔后发起新的请求消息。现实生活场景可能会使用相对较短的超时(几秒)和较长的发送间隔(例如,一分钟)。
The monitor implementation uses two separate timers: one to send Test Messages in specified intervals and another to flag an exception if a response does not arrive within the specified timeout period (see diagram). The Send Timer determines the time interval between the last received message or the last timeout event and sending the next Test Message. The Timeout Timer is started whenever the monitor sends a request message. If a reply message arrives within the specified timeout interval, the Timeout Timer is reset and restarted with the next request message. If the monitor does not receive a reply message within the specified interval, the Timeout Timer triggers and the monitor sends an error message to the control bus. It then starts a new Send Timer to initiate a new request message after the send interval. A real-life scenario is likely to use a relatively short timeout (a few seconds) and a longer send interval (e.g., one minute).
下图说明了两个定时器之间的依赖关系。在这种情况下,监视器发送测试消息并启动超时计时器。在计时器到期之前响应消息到达,因此监视器取消超时计时器并启动间隔计时器。当间隔定时器到期时,监视器发送新的测试消息并启动新的超时定时器。这次,超时计时器在回复消息到达之前到期,导致监视器向控制总线发送消息。同时,显示器启动新的间隔计时器。
The following figure illustrates the dependencies between the two timers. In this scenario, the monitor sends a Test Message and starts the Timeout Timer. A response message arrives before the timer elapses, so the monitor cancels the Timeout Timer and starts the Interval Timer. When the Interval Timer elapses, the monitor sends a new Test Message and starts a new Timeout Timer. This time, the Timeout Timer expires before the reply message arrives, causing the monitor to send a message to the Control Bus. At the same time, the monitor starts a new Interval Timer.
监控信用局服务
Monitoring the Credit Bureau Service
监视器的实现只需要一个类。Monitor类继承自智能代理模式中引入的MessageConsumer。此类配置入站通道并启动事件驱动的使用者来接收消息。对于每个传入消息,它都会调用虚拟ProcessMessage 方法。继承类可以简单地重写此方法来添加自己的处理。
The implementation of the monitor requires only a single class. The Monitor class inherits from the MessageConsumer introduced in the Smart Proxy pattern. This class configures an inbound channel and starts an Event-Driven Consumer to receive messages. For each incoming message, it invokes the virtual ProcessMessage method. An inheriting class can simply override this method to add its own processing.
Process方法指示MessageConsumer 开始消费消息。Monitor类通过启动发送计时器来增强此方法的基本实现。当此计时器触发时,它会调用OnSendTimerEvent方法。Process方法还将MonitorStatus 类型的消息发送到控制总线以宣布其存在。
The Process method instructs a MessageConsumer to start consuming messages. The Monitor class augments the base implementation of this method by starting the Send Timer. When this timer triggers, it invokes the OnSendTimerEvent method. The Process method also sends a message of type MonitorStatus to the Control Bus to announce its existence.
公共重写 void Process()
{
基.进程();
sendTimer = 新计时器(新 TimerCallback
(OnSendTimerEvent), null, 间隔*1000, Timeout.Infinite);
MonitorStatus 状态 = new MonitorStatus(
MonitorStatus.STATUS_ANNOUNCE, "监控在线", null, MonitorID);
Console.WriteLine(状态.描述);
controlQueue.Send(状态);
最后状态 = 状态.Status;
}
protected void OnSendTimerEvent(对象状态)
{
CreditBureauRequest 请求 = new CreditBureauRequest();
请求.SSN = SSN;
消息 requestMessage = new Message(请求);
requestMessage.Priority = MessagePriority.AboveNormal;
requestMessage.ResponseQueue = inputQueue;
Console.WriteLine(DateTime.Now.ToString() + "发送请求消息");
requestQueue.Send(requestMessage);
correlationID = requestMessage.Id;
timeoutTimer = new Timer(new TimerCallback(OnTimeoutEvent), null,
超时*1000,超时.无限);
}
public override void Process()
{
base.Process();
sendTimer = new Timer(new TimerCallback
(OnSendTimerEvent), null, interval*1000, Timeout.Infinite);
MonitorStatus status = new MonitorStatus(
MonitorStatus.STATUS_ANNOUNCE, "Monitor On-Line", null, MonitorID);
Console.WriteLine(status.Description);
controlQueue.Send(status);
lastStatus = status.Status;
}
protected void OnSendTimerEvent(Object state)
{
CreditBureauRequest request = new CreditBureauRequest();
request.SSN = SSN;
Message requestMessage = new Message(request);
requestMessage.Priority = MessagePriority.AboveNormal;
requestMessage.ResponseQueue = inputQueue;
Console.WriteLine(DateTime.Now.ToString() + " Sending request message");
requestQueue.Send(requestMessage);
correlationID = requestMessage.Id;
timeoutTimer = new Timer(new TimerCallback(OnTimeoutEvent), null,
timeout*1000, Timeout.Infinite);
}
OnSendTimerEvent方法创建一条新的请求消息。请求消息中的唯一参数是客户的 SSN。该方法指定固定的 SSN。该方法还保存消息 ID 以验证任何传入回复消息的相关标识符。最后,它启动timeoutTimer,以便在设定的时间间隔后如果没有收到回复消息,则通知监视器。
The OnSendTimerEvent method creates a new request message. The only parameter in the request message is the customer's SSN. The method specifies a fixed SSN. The method also saves the message ID to verify the Correlation Identifier of any incoming reply messages. Lastly, it starts the timeoutTimer so that the monitor is being notified after a set time interval if no reply message is received.
该方法将测试消息的Priority属性设置为BelowNormal,以确保排队的应用程序消息不会让服务看起来好像不可用。对测试消息使用较高的优先级会导致消息队列在排队的应用程序消息之前传送这些消息。在这种情况下,设置较高的消息优先级是安全的,因为测试数据生成器会注入非常少量的测试消息。如果我们将大量高优先级消息注入请求通道,则可能会中断应用程序消息流。这肯定会违反管理解决方案尽可能减少侵入的意图。
The method sets the test message's Priority property to AboveNormal to make sure that queued-up application messages do not let the service appear as if it is not available. Using a higher priority for Test Messages causes the message queue to deliver these messages ahead of queued-up application messages. Setting a higher message priority is safe in this case because the test data generator injects a very small volume of Test Messages. If we injected a large volume of high-priority messages into the request channel, we could interrupt the flow of application messages. This would definitely violate the intention of a management solution to be as minimally intrusive as possible.
ProcessMessage方法是Monitor。它实现测试消息验证器,评估传入的回复消息。停止超时计时器后,该方法检查传入消息的正确相关标识符、消息正文的正确数据类型以及消息正文中的合理值。如果这些测试中的任何一个失败,该方法都会设置一个MonitorStatus结构并将其发送到控制总线通道。监视器还跟踪存储在lastStatus变量中的先前状态。如果状态从“错误”更改为“正常”,则ProcessMessage方法还向控制总线发送通知。
The ProcessMessage method is the heart of the Monitor class. It implements the test message verifier, evaluating incoming reply messages. After stopping the timeout timer, the method checks the incoming message for the correct Correlation Identifier, correct datatype of the message body, and reasonable values inside the message body. If any of these tests fail, the method sets up a MonitorStatus structure and sends it to the Control Bus channel. The monitor also tracks the previous status that is stored in the lastStatus variable. If the status changes from "error" to "OK," the ProcessMessage method also sends a notification to the Control Bus.
受保护的覆盖无效 ProcessMessage(消息 msg)
{
Console.WriteLine(DateTime.Now.ToString() + "收到回复消息");
if (超时定时器!= null)
超时定时器.Dispose();
msg.Formatter = new XmlMessageFormatter(new Type[] {typeof(CreditBureauReply)});
CreditBureau回复replyStruct;
MonitorStatus 状态 = new MonitorStatus();
状态.Status = MonitorStatus.STATUS_OK;
status.Description = "无错误";
状态.ID = 监视器ID;
尝试
{
if (msg.Body 是 CreditBureauReply)
{
replyStruct = (CreditBureauReply)msg.Body;
if (msg.CorrelationId != correlationID)
{
状态.Status = MonitorStatus.STATUS_FAILED_CORRELATION;
状态.描述 =
“传入消息相关 ID 与传出消息 ID 不匹配”;
}
别的
{
if (replyStruct.CreditScore < 300 ||replyStruct.CreditScore > 900 ||
replyStruct.HistoryLength < 1 || replyStruct.HistoryLength > 24)
{
状态.Status = MonitorStatus.STATUS_INVALID_DATA;
status.Description = "信用评分值超出范围";
}
}
}
别的
{
状态.Status = MonitorStatus.STATUS_INVALID_FORMAT;
status.Description = "消息格式无效"; }
}
捕获(异常 e)
{
Console.WriteLine("异常:{0}", e.ToString());
状态.Status = MonitorStatus.STATUS_INVALID_FORMAT;
status.Description = "无法反序列化消息正文";
}
StreamReader reader = new StreamReader(msg.BodyStream);
status.MessageBody = reader.ReadToEnd();
Console.WriteLine(状态.描述);
if (status.Status != MonitorStatus.STATUS_OK ||
(status.Status == MonitorStatus.STATUS_OK &&
最后状态!= MonitorStatus.STATUS_OK))
{
controlQueue.Send(状态);
}
最后状态 = 状态.Status;
sendTimer.Dispose();
sendTimer = new Timer(new TimerCallback(OnSendTimerEvent), null,
间隔*1000,超时.无限);
}
protected override void ProcessMessage(Message msg)
{
Console.WriteLine(DateTime.Now.ToString() + " Received reply message");
if (timeoutTimer != null)
timeoutTimer.Dispose();
msg.Formatter = new XmlMessageFormatter(new Type[] {typeof(CreditBureauReply)});
CreditBureauReply replyStruct;
MonitorStatus status = new MonitorStatus();
status.Status = MonitorStatus.STATUS_OK;
status.Description = "No Error";
status.ID = MonitorID;
try
{
if (msg.Body is CreditBureauReply)
{
replyStruct = (CreditBureauReply)msg.Body;
if (msg.CorrelationId != correlationID)
{
status.Status = MonitorStatus.STATUS_FAILED_CORRELATION;
status.Description =
"Incoming message correlation ID does not match outgoing message ID";
}
else
{
if (replyStruct.CreditScore < 300 || replyStruct.CreditScore > 900 ||
replyStruct.HistoryLength < 1 || replyStruct.HistoryLength > 24)
{
status.Status = MonitorStatus.STATUS_INVALID_DATA;
status.Description = "Credit score values out of range";
}
}
}
else
{
status.Status = MonitorStatus.STATUS_INVALID_FORMAT;
status.Description = "Invalid message format"; }
}
catch (Exception e)
{
Console.WriteLine("Exception: {0}", e.ToString());
status.Status = MonitorStatus.STATUS_INVALID_FORMAT;
status.Description = "Could not deserialize message body";
}
StreamReader reader = new StreamReader (msg.BodyStream);
status.MessageBody = reader.ReadToEnd();
Console.WriteLine(status.Description);
if (status.Status != MonitorStatus.STATUS_OK ||
(status.Status == MonitorStatus.STATUS_OK &&
lastStatus != MonitorStatus.STATUS_OK))
{
controlQueue.Send(status);
}
lastStatus = status.Status;
sendTimer.Dispose();
sendTimer = new Timer(new TimerCallback(OnSendTimerEvent), null,
interval*1000, Timeout.Infinite);
}
如果在指定的时间间隔内没有消息到达,timeoutTimer将调用OnTimeoutEvent方法。该方法向控制总线发送一条MonitorStatus消息,并启动一个新的发送定时器,以便在该时间间隔后发送新的请求消息。
If no message arrives in the specified interval, the timeoutTimer will invoke the OnTimeoutEvent method. This method sends a MonitorStatus message to the Control Bus and starts a new Send Timer so that a new request message is sent after the interval.
protected void OnTimeoutEvent(对象状态)
{
MonitorStatus 状态 = new MonitorStatus(
MonitorStatus.STATUS_TIMEOUT, "超时", null, MonitorID);
Console.WriteLine(状态.描述);
controlQueue.Send(状态);
最后状态 = 状态.Status;
超时定时器.Dispose();
sendTimer = new Timer(new TimerCallback(OnSendTimerEvent), null,
间隔*1000,超时.无限);
}
protected void OnTimeoutEvent(Object state)
{
MonitorStatus status = new MonitorStatus(
MonitorStatus.STATUS_TIMEOUT, "Timeout", null, MonitorID);
Console.WriteLine(status.Description);
controlQueue.Send(status);
lastStatus = status.Status;
timeoutTimer.Dispose();
sendTimer = new Timer(new TimerCallback(OnSendTimerEvent), null,
interval*1000, Timeout.Infinite);
}
现在我们可以监控外部信用局服务的状态,我们希望使用这些数据来实施故障转移方案,以便即使信用局服务出现故障,贷款经纪人也可以继续运行。值得注意的是,点对点通道已经提供了基本形式的故障转移。当我们在单个点对点通道上使用多个竞争消费者时,只要其他消费者仍在运行,一个消费者的故障就不会中断处理。当多个消费者处于活动状态时,它们会分割负载,有效地实现简单的负载平衡机制。那么为什么我们需要实施显式的故障转移机制呢?使用外部服务时,我们可能仅限于不支持竞争消费者的简单渠道,例如基于 HTTP 的 SOAP。此外,我们可能不希望多个服务进行负载平衡。例如,我们可能与主要服务提供商签订了批量协议,如果我们满足某些使用配额,该协议将为我们提供大幅折扣。将流量分散到两个提供商可能会花费更多。或者,我们可能使用低成本提供商作为我们的主要服务提供商,并且仅在低成本提供商失败时才希望切换到优质提供商。(有关由许可考虑因素驱动的架构决策的精彩讨论,请参阅 [ Hohmann ]。)
Now that we can monitor the status of the external credit bureau service, we want to use this data to implement a failover scheme so that the loan broker can continue operating even when the credit bureau service fails. It is worthwhile noting that Point-to-Point Channels already provide a basic form of failover. When we use multiple Competing Consumers on a single Point-to-Point Channel, the failure of one consumer will not interrupt processing as long as the other consumer(s) still operate. When multiple consumers are active, they split the load, effectively implementing a simple load-balancing mechanism. Why then would we need to implement an explicit failover mechanism? When using external services, we may be limited to simple channels that do not support Competing Consumers, such as SOAP over HTTP. Also, we may not want multiple services to load balance. For example, we may have a volume agreement with the primary service provider that gives us substantial discounts if we meet certain usage quotas. Splitting the traffic across two providers will likely cost us more. Alternatively, we may be using a low-cost provider as our primary service provider and want to switch over to a premium provider only when the low-cost provider fails. (For an excellent discussion of architectural decisions driven by licensing considerations, see [Hohmann].)
为了实现显式故障转移,我们将消息路由器插入信用局请求通道(见图)。该路由器将请求路由到主要信用局服务(粗、黑色箭头)或辅助信用局服务(细、黑色箭头)。由于辅助服务可能使用与第一个服务不同的消息格式,因此我们用一对消息转换器包装辅助服务。消息路由器是基于上下文的消息路由器,由管理控制台通过控制总线控制。管理控制台从我们在上一节中设计的信用局监视器获取监控数据。如果监视器指示出现故障,管理控制台会指示消息路由器将流量重新路由到辅助服务提供商(见图)。
In order to implement explicit failover, we insert a Message Router into the credit bureau request channel (see figure). This router routes the request either to the primary credit bureau service (thick, black arrows) or to the secondary credit bureau service (thin, black arrows). Because the secondary service may use a different message format than the first service, we wrap the secondary service with a pair of Message Translators. The Message Router is a context-based Message Router controlled by the management console over the Control Bus. The management console gets monitoring data from the credit bureau monitor we designed in the previous section. If the monitor indicates a failure, the management console instructs the Message Router to reroute the traffic to the secondary service provider (see figure).
使用基于上下文的消息路由器进行显式故障转移
Explicit Failover with a Context-Based Message Router
当请求消息流量重新路由到辅助服务提供商时,监控器继续向主要提供商发送测试消息。当监视器确认服务操作正确时,控制台指示消息路由器返回将请求消息路由到主要提供者。该解决方案图没有显示辅助服务提供商的监视器,尽管使用信用局监视器的第二个实例来监视备份信用局服务的运行状况非常容易。
While the request message traffic is rerouted to the secondary service provider, the monitor keeps on sending test messages to the primary provider. When the monitor confirms the correct operation of the service, the console instructs the Message Router to return to routing request messages to the primary provider. The solution diagram does not show a monitor for the secondary service provider even though it would be very easy to use a second instance of the credit bureau monitor to monitor the health of the backup credit bureau service.
我们来看一下基于上下文的消息路由器的实现。ContextBasedRouter类继承自我们值得信赖的MessageConsumer基类来处理传入消息。ProcessMessage方法检查变量控件,并根据变量的值将传入消息路由到主要或辅助输出通道。
Let's look at the implementation of the context-based Message Router. The ContextBasedRouter class inherits from our trusty MessageConsumer base class to process incoming messages. The ProcessMessage method checks the value of the variable control and routes incoming messages to either the primary or secondary output channel based on the value of the variable.
委托无效 ControlEvent(int control);
ContextBasedRouter 类:MessageConsumer
{
...
受保护的覆盖无效 ProcessMessage(消息 msg)
{
如果(控制== 0)
{
PrimaryOutputQueue.Send(msg);
}
别的
{
secondaryOutputQueue.Send(msg);
}
}
受保护的无效OnControlEvent(int控制)
{
this.control = 控制;
Console.WriteLine("Control = " + control);
}
}
delegate void ControlEvent(int control);
class ContextBasedRouter : MessageConsumer
{
...
protected override void ProcessMessage(Message msg)
{
if (control == 0)
{
primaryOutputQueue.Send(msg);
}
else
{
secondaryOutputQueue.Send(msg);
}
}
protected void OnControlEvent(int control)
{
this.control = control;
Console.WriteLine("Control = " + control);
}
}
变量控件由OnControlEvent 方法设置。此方法由ControlReceiver类调用,该类也继承自MessageConsumer,因为它侦听来自控制通道的消息。ContextBasedRouter类为ControlReceiver提供ControlEvent类型的委托,以便在收到带有数值的控件事件时调用。如果您还没有遇到过委托,那么它们是一种非常简洁、类型安全的实现回调的方法,而无需实现另一个接口或降级为函数指针([Box]涉及所有血淋淋的细节)。
The variable control is set by the OnControlEvent method. This method is invoked by the ControlReceiver class, which also inherits from MessageConsumer as it listens for messages from the control channel. The ContextBasedRouter class supplies the ControlReceiver with a delegate of type ControlEvent to invoke when it receives a control event with a numeric value. If you have not come across delegates, they are a really neat, type-safe way to implement callbacks without having to implement another interface or relegating to function pointers ([Box] goes into all the gory details).
类 ControlReceiver : MessageConsumer
{
受保护的 ControlEvent 控制事件;
公共ControlReceiver(消息队列输入队列,
ControlEvent controlEvent) : 基(inputQueue)
{
this.controlEvent = controlEvent;
}
受保护的覆盖无效 ProcessMessage(消息 msg)
{
字符串文本 = (字符串)msg.Body;
双倍resNum;
if (Double.TryParse(文本,NumberStyles.Integer,
NumberFormatInfo.InvariantInfo,输出 resNum))
{
int 控制 = int.Parse(text);
控制事件(控制);
}
}
}
class ControlReceiver : MessageConsumer
{
protected ControlEvent controlEvent;
public ControlReceiver(MessageQueue inputQueue,
ControlEvent controlEvent) : base (inputQueue)
{
this.controlEvent = controlEvent;
}
protected override void ProcessMessage(Message msg)
{
String text = (string)msg.Body;
Double resNum;
if (Double.TryParse(text, NumberStyles.Integer,
NumberFormatInfo.InvariantInfo, out resNum))
{
int control = int.Parse(text);
controlEvent(control);
}
}
}
管理控制台的第一个版本非常简单,我们甚至懒得展示代码。它所能做的就是接收消息并将消息内容写入文件以供以后分析(例如从 Excel 渲染性能图表)。现在我们想为管理控制台注入更多智能。首先,当主信用局监视器指示故障时,管理控制台需要指示基于上下文的消息路由器将消息重新路由到辅助服务提供商。我们选择在管理控制台内部实现此功能,以便我们可以有效地解耦监视器和基于上下文的消息路由器,管理控制台充当中介者[ GoF ]。此外,在中央位置实施故障转移逻辑为我们提供了系统管理规则的单点维护。商业管理控制台通常包括可配置的规则引擎,用于根据控制总线上的事件确定适当的纠正措施。
The first version of the management console was so simple that we did not even bother showing the code. All it could do was receive a message and write the message content to a file for later analysis (such as rendering performance graphs from Excel). Now we want to inject some more intelligence into the management console. First, when the primary credit bureau monitor indicates a failure, the management console needs to instruct the context-based Message Router to reroute messages to the secondary service provider. We opted to implement this functionality inside the management console so that we can decouple the monitor and the context-based Message Router effectively, the management console acts as a Mediator [GoF]. Also, implementing the failover logic in a central location gives us a single point of maintenance for the system management rules. Commercial management consoles typically include configurable rules engines to determine appropriate corrective actions based on events on the Control Bus.
其次,我们希望为管理控制台构建一个简单的用户界面,显示系统的当前状态。获得消息传递系统的全局视图可能相当困难,尤其是在消息路径动态变化的情况下。即使组件数量很少,也可能导致协调消息流向变得困难。我们的用户界面很简单,但仍然非常有用。我们使用本书中定义的标志性语言来表示组件之间的交互。目前,用户界面仅显示系统的信用局故障转移部分,由两项服务和一个基于上下文的消息路由器组成;参见下页图。
Second, we want to build a simple user interface for the management console that displays the current state of the system. Obtaining a big-picture view of a messaging system can be quite difficult, especially if message paths change dynamically. Even a small number of components can make it difficult to reconcile where messages flow. Our user interface is simple but nevertheless quite useful. We use the iconic language defined in this book to represent the interaction between components. For now, the user interface displays only the credit bureau failover portion of the system, consisting of two services and one context-based Message Router; see figure on the following page.
管理控制台指示两个信用局服务均处于活动状态
The Management Console Indicates That Both Credit Bureau Services Are Active
当监视器检测到故障并指示路由器重新路由流量时,我们希望更新用户界面以反映新状态(见图)。路由器图标显示请求消息的新路由,主信用局组件会改变颜色以指示失败。
When the Monitor detects a failure and instructs the router to reroute the traffic, we want to update the user interface to reflect the new status (see figure). The router icon shows the new route for the request messages, and the primary credit bureau component changes colors to indicate the failure.
管理控制台指示主信用局发生故障且流量正在重新路由
The Management Console Indicates That the Primary Credit Bureau Failed and Traffic Is Being Rerouted
让我们简单看一下这个控制台背后的代码。我们专注于代码的系统管理部分,而不深入研究呈现漂亮的用户界面图片的代码的细节。首先,管理控制台需要能够从监视器组件检索状态消息。为了使控制台尽可能健壮,我们以松散耦合的方式访问消息内容,从 XML 有效负载中读取各个字段。即使各个组件决定向消息格式添加新字段,此方法也有助于保持管理控制台的运行。
Let's have a brief look at the code behind this console. We focus on the system management part of the code and do not dive into the details of the code that renders the pretty user interface pictures. First, the management console needs to be able to retrieve status messages from the monitor component. To make the console as robust as possible, we access the message content in a loosely coupled fashion, reading individual fields from the XML payload. This approach helps keep the management console operational even if the individual components decide to add new fields to the message format.
毫不奇怪,控制台类也继承自我们的好朋友MessageConsumer ,因此我们只展示构造函数和ProcessMessage 方法的实现。该方法只是将消息的BodyStream 读取到字符串变量中,并将其传递给不同的组件进行分析。
Not surprisingly, the console class also inherits from our good friend MessageConsumer, so we only show the implementation of the constructor and the ProcessMessage method. The method simply reads the message's BodyStream into a string variable and passes it to the different components for analysis.
公共委托 void ControlMessageReceived(String body);
公共类管理控制台:MessageConsumer
{
受保护的 Logger 记录器;
公共 MonitorStatusHandler 监视器状态处理程序;
公共ControlMessage接收更新事件;
公共管理控制台(消息队列输入队列,字符串路径名称):基(输入队列)
{
logger = new Logger(路径名);
MonitorStatusHandler = new MonitorStatusHandler();
updateEvent += new ControlMessageReceived(logger.Log);
updateEvent += new ControlMessageReceived(monitorStatusHandler.OnControlMessage);
}
protected override void ProcessMessage(消息 m)
{
流 stm = m.BodyStream;
StreamReader 阅读器 = new StreamReader (stm);
字符串主体 = reader.ReadToEnd();
更新事件(主体);
}
...
}
public delegate void ControlMessageReceived(String body);
public class ManagementConsole : MessageConsumer
{
protected Logger logger;
public MonitorStatusHandler monitorStatusHandler;
public ControlMessageReceived updateEvent;
public ManagementConsole(MessageQueue inputQueue, string pathName) : base(inputQueue)
{
logger = new Logger(pathName);
monitorStatusHandler = new MonitorStatusHandler();
updateEvent += new ControlMessageReceived(logger.Log);
updateEvent += new ControlMessageReceived(monitorStatusHandler.OnControlMessage);
}
protected override void ProcessMessage(Message m)
{
Stream stm = m.BodyStream;
StreamReader reader = new StreamReader (stm);
String body = reader.ReadToEnd();
updateEvent(body);
}
...
}
ManagementConsole类使用委托来通知记录器和MonitorStatusHandler 。使用委托使我们能够轻松添加其他类,这些类也侦听传入的控制消息,而无需更改ProcessMessage 方法内的代码。
The ManagementConsole class uses a delegate to notify the logger and the MonitorStatusHandler. Using a delegate allows us to easily add other classes that also listen on incoming control messages without having to change the code inside the ProcessMessage method.
MonitorStatusHandler 类是分析传入控制消息数据的组件之一。 首先,此类检查传入消息正文中包含的 XML 文档是否具有根元素 < MonitorStatus>。如果是,它将消息正文加载到 XML 文档中,以提取 ID和Status元素中包含的相关字段。然后,它调用委托updateEvent,其类型为MonitorStatusUpdate 。管理控制台应用程序中任何感兴趣的类都可以向此委托添加回调方法,并在MonitorStatus发生时随时收到通知消息到达。该组件所要做的就是提供一个签名等于MonitorStatusUpdate 的方法的实现。
One of the components analyzing incoming control message data is the MonitorStatusHandler class. First, this class checks whether the XML document contained in the body of the incoming message has the root element <MonitorStatus>. If so, it loads the message body into an XML document to extract the relevant fields contained inside the ID and the Status elements. It then invokes the delegate updateEvent, which is of type MonitorStatusUpdate. Any interested class inside the management console application can add a callback method to this delegate and be notified any time a MonitorStatus message arrives. All the component has to do is provide an implementation of a method with a signature equal to MonitorStatusUpdate.
公共委托 void MonitorStatusUpdate(String ID, int Status);
公共类 MonitorStatusHandler
{
公共监视器状态更新更新事件;
公共无效OnControlMessage(字符串体)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(正文);
XmlElement root = doc.DocumentElement;
if (root.Name == "MonitorStatus")
{
XmlNode statusNode = root.SelectSingleNode("状态");
XmlNode idNode = root.SelectSingleNode("ID");
if (idNode!= null && statusNode != null)
{
String msgID = idNode.InnerText;
String msgStatus = statusNode.InnerText;
双倍resNum;
整数状态= 99;
if (Double.TryParse(msgStatus, NumberStyles.Integer,
NumberFormatInfo.InvariantInfo,输出 resNum))
{
状态 = (int)resNum;
}
updateEvent(msgID, 状态);
}
}
}
}
public delegate void MonitorStatusUpdate(String ID, int Status);
public class MonitorStatusHandler
{
public MonitorStatusUpdate updateEvent;
public void OnControlMessage(String body)
{
XmlDocument doc = new XmlDocument();
doc.LoadXml(body);
XmlElement root = doc.DocumentElement;
if (root.Name == "MonitorStatus")
{
XmlNode statusNode = root.SelectSingleNode("Status");
XmlNode idNode = root.SelectSingleNode("ID");
if (idNode!= null && statusNode != null)
{
String msgID = idNode.InnerText;
String msgStatus = statusNode.InnerText;
Double resNum;
int status = 99;
if (Double.TryParse(msgStatus, NumberStyles.Integer,
NumberFormatInfo.InvariantInfo, out resNum))
{
status = (int)resNum;
}
updateEvent(msgID, status);
}
}
}
}
在我们的示例中,侦听由 MonitorStatusHandler触发的MonitorStatusUpdate 事件的前两个组件是两个用户界面控件,代表用户界面表单中的主要和辅助信用局服务。每个用户界面控件都会过滤事件以获取对于正在监视的相应组件唯一的标识符。当受监视组件的状态发生变化时,用户界面控件会更改组件的颜色。以下例程在显示窗体初始化期间执行,并将两个信用局显示控件绑定到管理控制台的 MonitorStatusHandler 。这段代码导致该方法每当控制台收到监视器状态更新消息时调用的控件的 OnMonitorStatusUpdate
In our example, the first two components listening to the MonitorStatusUpdate event triggered by the MonitorStatusHandler are two user interface controls representing the primary and secondary credit bureau service in the user interface form. Each user interface control filters the events for the identifier that is unique to the respective component that is being monitored. When the status of the monitored component changes, the user interface control changes the color of the component. The following routine executes during the initialization of the display form and ties the two credit bureau display controls to the monitorStatusHandler of the management console. This code causes the method OnMonitorStatusUpdate of the controls to be invoked whenever the console receives a monitor status update message
console = new ManagementConsole(controlBusQueue, logFileName);
主要信用局控制 =
new ComponentStatusControl("Primary Credit Bureau", "PrimaryCreditService");
PrimaryCreditBureauControl.Bounds =
新矩形(300, 30, 组件宽度, 组件高度);
二级信用局控制 =
new ComponentStatusControl("二级征信局", "SecondaryCreditService");
secondaryCreditBureauControl.Bounds =
新矩形(300、130、分量宽度、分量高度);
console.monitorStatusHandler.updateEvent += new
MonitorStatusUpdate(primaryCreditBureauControl.OnMonitorStatusUpdate);
console.monitorStatusHandler.updateEvent += new
MonitorStatusUpdate(secondaryCreditBureauControl.OnMonitorStatusUpdate);
console = new ManagementConsole(controlBusQueue, logFileName);
primaryCreditBureauControl =
new ComponentStatusControl("Primary Credit Bureau", "PrimaryCreditService");
primaryCreditBureauControl.Bounds =
new Rectangle(300, 30, COMPONENT_WIDTH, COMPONENT_HEIGHT);
secondaryCreditBureauControl =
new ComponentStatusControl("Secondary Credit Bureau", "SecondaryCreditService");
secondaryCreditBureauControl.Bounds =
new Rectangle(300, 130, COMPONENT_WIDTH, COMPONENT_HEIGHT);
console.monitorStatusHandler.updateEvent += new
MonitorStatusUpdate(primaryCreditBureauControl.OnMonitorStatusUpdate);
console.monitorStatusHandler.updateEvent += new
MonitorStatusUpdate(secondaryCreditBureauControl.OnMonitorStatusUpdate);
另一个监听MonitorStatusUpdate 事件的组件是FailOverHandler 。该组件是一个非可视组件,它分析状态消息以确定是否应设置故障转移开关。如果监视器的状态发生了变化(我们使用由^运算符表示的逻辑 XOR),则FailOverHandler 会向指定的命令通道发送命令消息。在我们的例子中,此命令通道连接到前面描述的基于上下文的消息路由器,该路由器将开始将信用评分请求消息重新路由到不同的信用局提供商。
Another component listening to the MonitorStatusUpdate events is the FailOverHandler. This component is a nonvisual component that analyzes status messages to determine whether a failover switch should be set. If the status of the monitor has changed (we use a logical XOR denoted by the ^ operator), the FailOverHandler sends a command message to the designated command channel. In our case, this command channel is connected to the context-based Message Router described earlier, which will start rerouting credit score request messages to a different credit bureau provider.
公共委托 void FailOverStatusUpdate(String ID, string Command);
公共类故障转移处理程序
{
...
公共无效OnMonitorStatusUpdate(字符串ID,int状态)
{
if (组件ID == ID)
{
if (IsOK(状态) ^ IsOK(当前状态))
{
字符串命令 = IsOK(状态) ? “0”:“1”;
命令队列.Send(命令);
当前状态=状态;
updateEvent(ID, 命令);
}
}
}
protected bool IsOK(int 状态)
{
返回(状态 == 0 || 状态 >= 99);
}
}
public delegate void FailOverStatusUpdate(String ID, string Command);
public class FailOverHandler
{
...
public void OnMonitorStatusUpdate(String ID, int status)
{
if (componentID == ID)
{
if (IsOK(status) ^ IsOK(currentStatus))
{
String command = IsOK(status) ? "0" : "1";
commandQueue.Send(command);
currentStatus = status;
updateEvent(ID, command);
}
}
}
protected bool IsOK(int status)
{
return (status == 0 || status >= 99);
}
}
FailOverHandler还调用updateEvent ,它是FailOverStatusUpdate。与 MonitorStatusHandler 类似,我们可以注册任何实现此类型方法的组件,以便在FailOverHandler 更改状态时接收更新通知。在我们的示例中,我们注册视觉FailOverControl 来接收这些事件,以便在故障转移状态发生变化时它可以重新绘制。控制台用户界面初始化例程在这些组件之间建立连接:
The FailOverHandler also invokes the updateEvent, which is a delegate of type FailOverStatusUpdate. Similar to the MonitorStatusHandler, we can register any component that implements a method of this type to receive update notifications whenever the FailOverHandler changes status. In our example, we register the visual FailOverControl to receive these events so that it can redraw whenever the failover status changes. The console user interface initialization routine establishes the connection between these components:
failOverControl = new FailOverControl("信用局故障转移", "PrimaryCreditService");
failOverControl.Bounds = new 矩形(100, 80, ROUTER_WIDTH, COMPONENT_HEIGHT);
故障处理程序 故障处理程序 =
新的 FailOverHandler(commandQueue, "PrimaryCreditService");
console.monitorStatusHandler.updateEvent +=
新的 MonitorStatusUpdate(failOverHandler.OnMonitorStatusUpdate);
failOverHandler.updateEvent += 新
FailOverStatusUpdate(failOverControl.OnMonitorStatusUpdate);
failOverControl = new FailOverControl("Credit Bureau Failover", "PrimaryCreditService");
failOverControl.Bounds = new Rectangle(100, 80, ROUTER_WIDTH, COMPONENT_HEIGHT);
FailOverHandler failOverHandler =
new FailOverHandler(commandQueue, "PrimaryCreditService");
console.monitorStatusHandler.updateEvent +=
new MonitorStatusUpdate(failOverHandler.OnMonitorStatusUpdate);
failOverHandler.updateEvent += new
FailOverStatusUpdate(failOverControl.OnMonitorStatusUpdate);
通过委托和事件连接管理控制台内的各个组件会形成松散耦合的体系结构。这种架构允许我们重用各个组件,并将它们重新组合成不同的星座,类似于本书开头介绍的管道和过滤器架构风格。本质上,使用委托传递到达控制总线的消息类似于创建应用程序内部的发布-订阅通道。由于控制总线事件到达点对点通道,因此我们必须使用单个使用者,然后该使用者将事件发布到应用程序内任何感兴趣的订阅者。
Connecting the individual components inside the management console through delegates and events results in a loosely coupled architecture. This architecture allows us to reuse the individual components and recompose them into different constellations similar to the Pipes and Filters architectural style introduced at the beginning of the book. Essentially, passing messages arriving on the Control Bus by using delegates resembles creating an application-internal Publish-Subscribe Channel. Because the control bus events arrive on a Point-to-Point Channel, we have to use a single consumer, which then publishes the event to any interested subscriber inside the application.
以下协作图说明了管理控制台内各个组件之间的事件传播。
The following collaboration diagram illustrates the propagation of events between the individual components inside the management console.
管理控制台内事件的传播
Propagation of Events Inside the Management Console
使用用户界面控制台可视化各个组件之间的消息流是一种强大的系统管理工具。一些供应商提供的开发套件允许设计人员直观地排列组件并连接其输入和输出端口以创建分布式消息流应用程序。例如,福州共赢靓号网络科技有限公司的Tifosi产品(http://www.fiorano.com)包括分布式应用程序编辑器,即使每个组件可能在不同的机器或平台上执行,它也允许从单个GUI设计分布式解决方案。该工具使用控制总线将所有分布式组件连接到中央管理和监控控制台。
Using a user interface console to visualize the message flow between individual components is a powerful systems management tool. Some vendors include development suites that allow designers to visually arrange components and connect their input and outputs ports to create distributed message-flow application. For example, Fiorano's Tifosi product (http://www.fiorano.com) includes the Distributed Applications Composer that allows the design of a distributed solution from a single GUI even though each component may execute on a different machine or platform. This tool uses a Control Bus to connect all distributed components to a central management and monitoring console.
我们的简单示例要求管理控制台对各个组件之间的可视连接进行硬编码,例如,在故障转移路由器和代表信用局服务的图标之间画一条线。许多集成工具允许用户使用 GUI 从一开始就设计解决方案。这种方法可以轻松地使用相同的图形设计来显示解决方案的状态。
Our simple example requires the management console to hard-code the visual connection between the individual components, for example, to draw a line between the failover router and the icons representing the credit bureau services. Many integration tools allow the user to design the solution from the beginning using a GUI. This approach makes it easy to use the same graphical design to display the status of the solution.
或者,我们可以分析现有消息传递解决方案中的消息流以创建系统的图形表示。有两种基本方法可以执行此类分析:静态和动态。静态分析检查每个组件发布和订阅的通道。如果一个组件发布到另一组件订阅的同一通道,则该工具可以在两个组件之间绘制一条连接线。许多 EAI 工具套件(例如 TIBCO ActiveEnterprise)将此类信息存储在中央存储库中,从而使此类分析变得更加容易。第二种方法使用动态分析通过检查流经系统的各个消息,并根据到达特定组件的消息来源对组件之间的连接进行逆向工程。如果系统中的消息包含消息历史记录,则此任务将大大简化。如果没有消息历史记录的帮助,如果每条消息都包含指定消息发送者的字段(许多系统包含这样的字段用于身份验证目的),我们仍然可以重建消息流。
Alternatively, we can analyze the message flow in an existing messaging solution to create a graphical representation of the system. There are two fundamental approaches to perform this type of analysis: static and dynamic. A static analysis examines the channels that each component publishes and subscribes to. If one component publishes to the same channel another component subscribes to, the tool can draw a connecting line between the two components. Many EAI tool suites, for example, TIBCO ActiveEnterprise, store this type of information in a central repository, making this type of analysis much easier. The second approach uses dynamic analysis by inspecting individual messages flowing through the system and reverse-engineering connections between components based on the origin of messages arriving at a particular component. This task is greatly simplified if the messages in the system contain a Message History. Without the help of a Message History, we can still reconstruct the flow of messages if each message contains a field specifying the sender of the message (many systems include such a field for authentication purposes).
为了使这个例子适合一章的范围,我们必须做出一些简化的假设。例如,当主信用局服务出现故障时,我们的故障转移机制不会处理已经排队的消息,这些消息将保持排队状态,直到服务恢复为止。贷款经纪人能够继续运行,因为它将传入的响应消息与回复消息相关联,但与“卡住”消息关联的贷款报价请求将不会得到处理,直到主信用局服务重新上线。为了提高故障转移场景中的响应时间,我们应该实现一个重新发送功能,允许贷款经纪人为那些在失败的服务之前无限期排队的消息重新发出请求消息。或者,故障转移路由器可以存储自上次确认服务的正确功能以来到达的所有请求消息。如果检测到服务故障,路由器可能会重新发送所有这些消息,因为其中一些消息可能未得到正确处理。这种方法可能会导致重复的请求消息(以及相关的回复消息),但由于信用局服务和贷款经纪人都是 如果检测到服务故障,路由器可能会重新发送所有这些消息,因为其中一些消息可能未得到正确处理。这种方法可能会导致重复的请求消息(以及相关的回复消息),但由于信用局服务和贷款经纪人都是 如果检测到服务故障,路由器可能会重新发送所有这些消息,因为其中一些消息可能未得到正确处理。这种方法可能会导致重复的请求消息(以及相关的回复消息),但由于信用局服务和贷款经纪人都是幂等接收器这不会导致任何问题,重复的回复消息将被简单地忽略。
In order to fit this example into the scope of a single chapter, we had to make some simplifying assumptions. For example, our failover mechanism does not deal with the messages that are already queued up when the primary credit bureau service failsthese messages remain queued up until the service is reinstated. The loan broker is able to continue functioning because it correlates incoming response messages to reply messages, but the loan quote requests associated with the "stuck" messages will not be processed until the primary credit bureau service comes back online. In order to improve response times in a failover scenario, we should implement a resend function that allows the loan broker to reissue request messages for those messages that are queued up indefinitely in front of a failed service. Alternatively, the failover router could store all request messages that have arrived since the correct function of the service was last confirmed. If a service failure is detected, the router could resend all these messages because some of them might not have been processed correctly. This approach can lead to duplicate request messages (and associated reply messages), but since both the credit bureau service and the loan broker are Idempotent Receivers this does not cause any problemsduplicate reply messages are simply ignored.
此示例仅演示了可以使用前一章中的模式实现的系统管理功能的一小部分。例如,我们可以监控所有组件的消息流量、设置性能阈值、让每个组件发送心跳消息等等。事实上,向分布式消息传递解决方案添加强大的系统管理可能需要与原始解决方案一样多(或更多)的设计和实现工作。
This example demonstrated only a small subset of the system management functions that can be implemented with the patterns in the previous chapter. For example, we could monitor message traffic across all components, set performance thresholds, have each component send heartbeat messages, and more. In fact, adding robust systems management to a distributed messaging solution can require as much (or more) design and implementation effort as the original solution.
作者:乔纳森·西蒙
by Jonathan Simon
您很容易远离大量模式或模式语言。模式是可重用形式的想法的抽象。通常,模式的通用性使其非常有用,但也使其难以掌握。有时,帮助理解模式的最好方法是一个现实世界的例子,而不是可能发生的人为场景,而是实际发生的情况和将要发生的情况。
It is easy to distance yourself from a large collection of patterns or a pattern language. Patterns are the abstraction of an idea in a reusable form. Often, the very generic nature of patterns that makes them so useful also makes them hard to grasp. Sometimes, the best thing to help understand patterns is a real-world examplenot a contrived scenario of what could happen, but what actually happens and what will happen.
本章通过发现过程应用模式来解决问题。我们讨论的系统是一个债券交易系统,我从最初的设计到生产使用了两年。我们探索遇到的场景和问题以及如何用模式解决它们。这涉及选择模式的决策过程以及如何组合和调整模式以满足系统的需求。这一切都是在考虑到实际系统中遇到的压力的情况下完成的,包括业务需求、客户决策、架构和技术要求以及遗留系统集成。这种方法的目的是通过实际应用提供对模式本身的更清晰的理解。
This chapter applies patterns to solve problems using a discovery process. The system we discuss is a bond trading system that I worked with for two years from initial design through production. We explore scenarios and problems that were encountered and how to solve them with patterns. This involves the decision process of choosing a pattern as well as how to combine and adjust patterns to suit the needs of the system. This is all done taking into account the forces encountered in real systems, including business requirements, client decisions, architectural and technical requirements, and legacy system integration. The intent of this approach is to provide a clearer understanding of the patterns themselves through practical application.
华尔街一家大型投资银行着手建立债券定价系统,以简化其债券交易柜台的工作流程。目前,债券交易商必须将大量债券的价格发送到几个不同的交易场所,每个交易场所都有自己的用户界面。该系统的目标是最大限度地减少所有债券定价的细节,并在单个封装的用户界面中结合特定于债券市场的高级分析功能。这意味着通过各种通信协议与多个组件进行集成和通信。系统的高级流程如下所示:
A major Wall Street investment bank sets out to build a bond pricing system in an effort to streamline the workflow of its bond trading desk. Currently, bond traders have to send prices for a large number of bonds to several different trading venues, each with its own user interface. The goal for the system is to minimize the minutiae of pricing all of the bonds combined with advanced analytic functionality specific to the bond market in a single encapsulated user interface. This means integration and communication with several components over various communications protocols. The high-level flow of the system looks like this:
高层次流程
High-Level Flow
首先,市场数据进入系统。市场数据是有关债券价格和其他属性的数据,代表人们愿意在自由市场上买卖债券的价格。市场数据立即发送到更改数据的分析引擎。分析是指用于改变债券价格和其他属性的金融应用的数学函数。这些是通用函数,它们使用输入变量来根据特定键定制函数的结果。将在每个交易者桌面上运行的客户端应用程序将根据每个交易者配置分析引擎,控制交易者定价的每种债券的分析细节。一旦将分析应用于市场数据,
First, market data comes into the system. Market data is data regarding the price and other properties of the bond, representing what people are willing to buy and sell the bond for on the free market. The market data is immediately sent to the analytics engine that alters the data. Analytics refers to mathematical functions for financial applications that alter the prices and other attributes of bonds. These are generic functions that use input variables to tailor the results of the function to a particular bond. The client application that will run on each trader desktop will configure the analytics engine on a per-trader basis, controlling the specifics of the analytics for each bond the trader is pricing. Once the analytics are applied to the market data, the modified data is sent out to various trading venues where traders from other firms can buy or sell the bonds.
通过对系统中数据流的概述,我们可以解决在设计过程中遇到的一些架构问题。让我们看看迄今为止我们所知道的情况。交易者需要在 Windows NT 和 Solaris 工作站上具有非常灵敏的应用程序。因此,我们决定将客户端应用程序实现为 Java 胖客户端,因为它具有平台无关性并且能够快速响应用户输入和市场数据。在服务器端,我们继承了我们的系统将使用的遗留 C++ 组件。市场数据组件与 TIBCO 信息总线 (TIB) 消息传递基础设施进行通信。
With this overview of the data flow in the system, we can approach some of the architectural problems we encounter during the design process. Let's take a look at what we know to date. Traders need a very responsive application on both Windows NT and Solaris workstations. Therefore, we decided to implement the client application as a Java thick client because of its platform-independence and its ability to quickly respond to user input and market data. On the server side, we are inheriting legacy C++ components that our system will utilize. The market data components communicate with the TIBCO Information Bus (TIB) messaging infrastructure.
我们继承以下组件:
We are inheriting the following components:
市场数据喂价服务器: 将传入的市场数据发布到 TIB。
Market Data Price Feed Server: Publishes incoming market data to the TIB.
分析引擎: 对传入的市场数据进行分析并将修改后的市场数据广播到 TIB。
Analytics Engine: Performs analytics on incoming market data and broadcasts the modified market data to the TIB.
贡献服务器: 执行与交易场所的所有通信。交易场所是不受银行控制的第三方组件。
Contribution Server: Performs all communication with trading venues. The trading venues are third-party components not controlled by the bank.
遗留市场数据子系统
Legacy Market Data Subsystem
遗留贡献子系统
Legacy Contribution Subsystem
我们需要决定各个子系统(Java 胖客户端、市场数据和贡献)如何进行通信。我们可以让胖客户端直接与遗留服务器通信,但这需要客户端上有太多的业务逻辑。相反,我们将构建一对 Java 网关来与旧服务器进行通信:市场数据的定价网关和用于将价格发送到交易场所的贡献网关。这将实现与这些领域相关的业务逻辑的良好封装。系统中当前的组件如下所示。连接标记为“???” 表明我们仍然不确定某些组件将如何通信。
We need to decide how the separate subsystems (Java thick client, market data, and contribution) are going to communicate. We could have the thick client communicate directly with the legacy servers, but that would require too much business logic on the client. Instead, we'll build a pair of Java gateways to communicate with the legacy serversthe pricing gateway for market data and the contribution gateway for sending prices to trading venues. This will achieve nice encapsulation of the business logic related to these areas. The current components in the system are shown below. The connections marked as "???" indicate that we are still unsure how some of the components will communicate.
系统及其组件
The System and Its Components
第一个通信问题是如何集成Java胖客户端和两个Java网关组件以交换数据。让我们看看本书中建议的四种集成样式:文件传输、共享数据库、远程过程调用和消息传递。 我们可以立即排除共享数据库,因为我们想在客户端和数据库之间创建一个抽象层,并且我们不希望客户端中有数据库访问代码。文件传输同样可以被排除,因为需要最小的延迟来确保将当前价格发送到交易场所。这让我们有一个选择远程过程调用和消息传递。
The first communication question is how to integrate the Java thick client and the two Java gateway components in order to exchange data. Let's look at the four integration styles suggested in this book: File Transfer, Shared Database, Remote Procedure Invocation, and Messaging. We can rule out Shared Database immediately because we want to create a layer of abstraction between the client and the database, and we don't want to have database access code in the client. File Transfer, can similarly be ruled out, since minimal latency is required to ensure current prices are sent out to the trading venues. This leaves us with a choice between Remote Procedure Invocation and Messaging.
Java 平台为远程过程调用和消息传递提供内置支持。RPC 风格的集成可以使用远程方法调用 (RMI)、CORBA 或 Enterprise JavaBeans (EJB) 来实现。Java 消息传递服务 (JMS) 是用于消息传递样式集成的通用 API。这两种集成风格都很容易用 Java 实现。
The Java platform provides built-in support for both Remote Procedure Invocation and Messaging. RPC-style integration can be achieved using Remote Method Invocation (RMI), CORBA, or Enterprise JavaBeans (EJB). The Java Messaging Service (JMS) is the common API for messaging-style integration. Both integration styles are easy to implement in Java.
那么,远程过程调用和消息传递哪个更适合这个项目呢?系统中只有一个定价网关实例和一个贡献网关实例,但通常有许多胖客户端同时连接到这些服务(每个恰好在特定时间登录的债券交易者对应一个)。此外,银行希望这是一个可以在其他应用程序中使用的通用定价系统。因此,除了未知数量的胖客户端之外,可能还有未知数量的其他应用程序使用来自网关的定价数据。
So, which will work better for this project, Remote Procedure Invocation or Messaging ? There's only one instance of the pricing gateway and one instance of the contribution gateway in the system, but usually many thick clients simultaneously connect to these services (one for each bond trader that happens to be logged in at a particular time). Furthermore, the bank would like this to be a generic pricing system that can be utilized in other applications. So, besides an unknown number of thick clients, there may be an unknown number of other applications using the pricing data coming out of the gateways.
胖客户端(或使用定价数据的其他应用程序)可以相当轻松地使用 RPC 调用网关来获取定价数据并调用处理。然而,定价数据会不断发布,而某些客户只对某些数据感兴趣,因此及时将相关数据提供给适当的客户可能会很困难。客户端可以轮询网关,但这会产生大量开销。网关最好在数据可用时立即向客户端提供数据。然而,这将要求每个网关跟踪哪些客户端当前处于活动状态以及哪些客户端需要哪些特定数据;然后,当有新的数据可用时(每秒会发生多次),网关必须向每个感兴趣的客户端发起 RPC,以将数据传递给客户端。理想情况下,所有客户端都应该同时收到通知,因此每个 RPC 都需要在自己的并发线程中进行。这可以工作,但是很快就会变得非常复杂。
A thick client (or other application using the pricing data) can fairly easily use RPC to make calls to the gateways to get pricing data and invoke processing. However, pricing data will constantly be published, and certain clients are only interested in certain data, so getting the relevant data to the proper clients in a timely manner could be difficult. The clients could poll the gateways, but that will create a lot of overhead. It would be better for the gateways to make the data available to the clients as soon as it is available. This, however, will require each gateway to keep track of which clients are currently active and which want what particular data; then, when a new piece of data becomes available (which will happen numerous times per second), the gateway will have to make an RPC to each interested client to pass the data to the client. Ideally, all clients should be notified simultaneously, so each RPC needs to be made in its own concurrent thread. This can work, but is getting very complicated very fast.
消息传递极大地通过消息传递,我们可以为不同类型的定价数据定义单独的渠道。然后,当网关获取新数据时,它将包含该数据的消息添加到的发布-订阅通道。同时,所有对某种类型的数据感兴趣的客户端都会在该类型的通道上监听。通过这种方式,网关可以轻松地将新数据发送给感兴趣的任何人,而无需知道有多少侦听器应用程序或它们是什么。
Messaging greatly simplifies this problem. With Messaging, we can define separate channels for the different types of pricing data. Then, when a gateway gets a new piece of data, it will add a message containing that data to the Publish-Subscribe Channel for that datatype. Meanwhile, all clients interested in a certain type of data will listen on the channel for that type. In this way, the gateways can easily send out new data to whomever is interested without needing to know how many listener applications there are or what they are.
客户端仍然需要能够调用网关中的行为。由于只有两个网关,并且在同步调用方法时客户端可能会阻塞,因此可以使用 RPC 相当轻松地实现这些客户端到网关的调用。然而,由于我们已经使用消息传递进行网关到客户端的通信,因此消息可能也是实现客户端到网关通信的好方法。
The clients still need to be able to invoke behavior in the gateways as well. Since there are ever only two gateways, and the client can probably block while the method is invoked synchronously, these client-to-gateway invocations can fairly easily be implemented using RPC. However, since we are already using messaging for gateway-to-client communication, messages are probably just as good a way to implement client-to-gateway communication as well.
因此,网关和客户端之间的所有通信都将通过消息传递来完成。由于所有组件都是用 Java 编写的,因此 JMS 为消息传递系统提供了一个简单的选择。这有效地创建了一个消息总线或一个体系结构,使未来的系统可以与当前系统集成,而无需对消息传递基础设施进行很少的更改或无需更改。这样,银行开发的其他应用程序就可以轻松使用该应用程序的业务功能。
Therefore, all communication between the gateways and the clients will be accomplished through messaging. Because all of the components are written in Java, JMS presents an easy choice for the messaging system. This is effectively creating a Message Bus or an architecture that will make it possible for future systems to integrate with the current system with little or no changes to the messaging infrastructure. This way, the business functionality of the application can be easily used by other applications the bank develops.
与JMS通信的 Java 组件
Java Components Communicating with JMS
JMS 只是一个规范,我们需要决定一个符合 JMS 的消息传递系统。我们决定使用 IBM MQSeries JMS,因为该银行是一家“IBM 商店”,使用 WebSphere 应用程序服务器和许多其他 IBM 产品。因此,我们将使用 MQSeries,因为我们已经拥有适当的支持基础设施和该产品的站点许可证。
JMS is simply a specification, and we need to decide on a JMS-compliant messaging system. We decided to use IBM MQSeries JMS because the bank is an "IBM shop," using WebSphere application servers and many other IBM products. As a result, we will use MQSeries, since we already have a support infrastructure in place and a site license of the product.
下一个问题是如何将 MQSeries 消息传递系统与独立的 C++ 贡献服务器以及基于 TIBCO 的市场数据和分析引擎服务器连接起来。我们需要一种方法让 MQSeries 使用者能够访问 TIB 消息。但如何呢?也许我们可以使用消息转换器模式将 TIB 消息转换为 MQSeries 消息。尽管 MQSeries 的 C++ 客户端充当消息转换器,但使用它会牺牲 JMS 服务器的独立性。尽管 TIBCO 确实有 Java API,但客户架构师和经理拒绝了它。因此,消息翻译器方法必须被放弃。
The next question is how to connect the MQSeries messaging system with the standalone C++ contribution server and the TIBCO-based market data and analytics engine servers. We need a way for the MQSeries consumers to have access to the TIB messages. But how? Perhaps we could use the Message Translator pattern to translate TIB messages into MQSeries messages. Although the C++ client for MQSeries serves as a Message Translator, using it would sacrifice JMS server independence. And although TIBCO does have a Java API, the customer architect and manager have rejected it. As a result, the Message Translator approach has to be abandoned.
从 TIB 服务器到 MQSeries 服务器的桥梁需要 C++ 和 Java 之间的通信。我们可以使用 CORBA,但是消息传递又如何呢?仔细观察消息转换器模式会发现它与通信协议的使用中的通道适配器相关。通道适配器的核心是将非消息系统连接到消息系统。连接两个消息传递系统的一对通道适配器是消息传递桥。
The bridge from the TIB server to the MQSeries server requires communication between C++ and Java. We could use CORBA, but then what about the messaging? A closer look at the Message Translator pattern shows it is related to the Channel Adapter in its use of communication protocols. The heart of a Channel Adapter is to connect non-messaging systems to messaging systems. A pair of channel adapters that connects two messaging systems is a Messaging Bridge.
消息传递桥的目的是将消息从一个消息传递系统传输到另一个消息传递系统。这正是我们正在做的事情,增加了语言内 Java 到 C++ 通信的复杂性。我们可以使用通道适配器和CORBA的组合来实现跨语言消息传递桥。我们将构建两个轻量级通道适配器服务器,一台使用 C++ 管理与 TIB 的通信,另一台使用 Java 管理与 JMS 的通信。这两个通道适配器,它们是消息端点他们自己将通过 CORBA 相互通信。就像我们选择 MQSeries 一样,我们将使用 CORBA 而不是 JNI,因为它是公司标准。消息桥实现了看似不兼容的消息系统和不同语言之间的有效模拟消息翻译。
The purpose of a Messaging Bridge is to transfer messages from one messaging system to another. This is exactly what we are doing with the added complexity of the intralanguage Java to C++ communication. We can implement the cross-language Messaging Bridge using a combination of Channel Adapters and CORBA. We will build two lightweight Channel Adapter servers, one in C++ managing communication with the TIB and one in Java managing communication with JMS. These two Channel Adapters, which are Message Endpoints themselves, will communicate with each other via CORBA. Like our choice for MQSeries, we will use CORBA rather than JNI, since it is a company standard. The messaging bridge implements the effectively simulated message translation between seemingly incompatible messaging systems and different languages.
使用通道适配器的消息传递桥
Messaging Bridge Using Channel Adapters
下页的下图显示了当前的系统设计,包括网关和其他组件。这是模式应用的一个很好的例子。我们将两个通道适配器与非消息传递协议相结合来实现消息传递桥模式,有效地使用一种模式来实现另一种模式。此外,我们更改了通道适配器的上下文,以使用非消息跨语言翻译协议链接两个消息系统,而不是将消息系统连接到非消息系统。
The next figure on the following page shows the current system design, including the gateways and other components. This is a good example of pattern application. We combined two Channel Adapters with a non-messaging protocol to implement the Messaging Bridge pattern, effectively using one pattern to implement another pattern. Additionally, we changed the Channel Adapters' context to link two messaging systems with a non-messaging cross-language translation protocol rather than connecting a messaging system to a non-messaging system.
带有通道适配器的当前系统
The Current System with the Channel Adapters
使用模式的关键不仅是知道何时使用哪种模式,而且还知道如何最有效地使用它。每个模式的实现都必须考虑技术平台的细节以及其他设计标准。本节应用相同的发现过程来找到在市场数据服务器与分析引擎通信的上下文中发布-订阅通道的最有效使用。
A key to working with patterns is knowing not only when to use which pattern, but also knowing how to most effectively use it. Each pattern implementation has to take into account specifics of the technology platform as well as other design criteria. This section applies the same discovery process to find the most efficient use of the Publish-Subscribe Channel in the context of the market data server communicating with the analytics engine.
实时市场数据源自市场数据源,这是一个在 TIB 上广播市场数据的 C++ 服务器。市场数据源为其发布价格的每种债券使用单独的发布-订阅通道。这可能看起来有点极端,因为每个新债券都需要自己的新渠道。但这并没有那么严重,因为您实际上不需要在 TIBCO 中创建通道。相反,频道由一组称为主题的分层主题名称引用。 然后,TIBCO 服务器按主题过滤单个消息流,将每个唯一的主题发送到单个虚拟通道。这导致了一个非常轻量级的消息通道。
Real-time market data originates with market data feed, a C++ server that broadcasts market data on the TIB. The market data feed uses a separate Publish-Subscribe Channel for each bond it is publishing prices for. This may seem a little extreme, since each new bond needs its own new channel. But this is not so severe, since you do not actually need to create channels in TIBCO. Rather, channels are referenced by a hierarchical set of topic names called subjects. The TIBCO server then filters a single message flow by subject, sending each unique subject to a single virtual channel. This results in a very lightweight message channel.
我们可以创建一个在几个频道上发布的系统,订阅者只能收听他们感兴趣的价格。这将要求订阅者使用消息过滤器或选择性消费者过滤整个数据流以获取感兴趣的债券价格,决定是否应在收到每条消息时对其进行处理。鉴于市场数据是在债券专用渠道上发布的,订阅者可以注册以获取一系列债券的最新信息。这有效地允许订阅者通过有选择地订阅频道并仅接收感兴趣的更新而不是在接收到消息后做出决定来“过滤”。值得注意的是,使用多个渠道来避免过滤是消息传递渠道的非标准使用。然而,在 TIBCO 技术的背景下,我们真正决定是实施我们自己的过滤器还是利用 TIBCO 内置的通道过滤,而不是是否使用这么多通道。
We could create a system that publishes on a few channels, and subscribers could listen only for prices they are interested in. This would require subscribers to use a Message Filter or Selective Consumer to filter the entire data flow for interesting bond prices, deciding whether each message should be processed as it is received. Given that the market data is published on bond-dedicated channels, subscribers can register for updates on a series of bonds. This effectively allows subscribers to "filter" by selectively subscribing to channels and only receiving updates of interest rather than deciding after the message is received. It is important to note that using multiple channels to avoid filtering is a nonstandard use of messaging channels. In context of the TIBCO technology, however, we are really deciding whether to implement our own filters or utilize the channel filtering built into TIBCOrather than whether to use so many channels.
我们需要设计的下一个组件是分析引擎,这是另一个 C++/TIB 服务器,它将修改市场数据并将其重新广播到 TIB。尽管它超出了我们的 Java/JMS 开发范围,但我们正在与 C++ 团队密切合作来设计它,因为我们是分析引擎的主要客户。当前的问题是找到最有效地转播新修改的市场数据的渠道结构。
The next component we need to design is the analytics engine, another C++/TIB server that will modify the market data and rebroadcast it to the TIB. Although it is out of the scope of our Java/JMS development, we are working closely with the C++ team to design it, since we are the analytics engine's primary customer. The problem at hand is to find the channel structure that most efficiently rebroadcasts the newly modified market data.
由于我们已经为每只债券拥有一个从市场数据价格馈送继承的专用消息通道,因此修改市场数据并在债券专用消息通道上重新广播修改后的市场数据是合乎逻辑的。但这是行不通的,因为修改债券价格的分析是针对特定交易者的。如果我们在 bond消息通道上重新广播修改后的数据,我们将通过用交易者特定的数据替换通用市场数据来破坏数据完整性。但是,我们可以为在同一渠道上发布的特定于交易者的市场数据提供不同的消息类型,从而允许订阅者决定他们感兴趣的消息,以避免破坏数据完整性。但随后客户将必须实施自己的过滤器来为其他交易者分离消息。此外,订阅者收到的消息也会大幅增加,给他们带来不必要的负担。
Since we already have one dedicated Message Channel per bond inherited from the market data price feed, it would be logical to modify the market data and rebroadcast the modified market data on the bond dedicated Message Channel. But this will not work since the analytics modifying the bonds prices are trader-specific. If we rebroadcast the modified data on the bond Message Channel, we will destroy the data integrity by replacing generic market data with trader-specific data. However, we could have a different message type for trader-specific market data that we publish on the same channel, allowing subscribers to decide which message they are interested in to avoid destroying the data integrity. But then clients will have to implement their own filters to separate out messages for other traders. Additionally, there will a substantial increase in messages received by subscribers, placing an unnecessary burden on them.
有两种选择:
There are two options:
每个交易者一个通道: 每个交易者都有一个指定的通道来修改市场数据。这样,原始市场数据保持不变,每个交易者应用程序都可以监听其特定交易者的消息通道以获取修改后的价格更新。
One channel per trader: Each trader has a designated channel for the modified market data. This way, the original market data remains intact, and each trader application can listen to its specific trader's Message Channel for the modified price updates.
每个交易者每个债券一个通道:为每个交易者每个债券 创建一个消息通道,仅用于该债券的修改后的市场数据。例如,债券 ABC 的市场数据将发布在频道“Bond ABC”上,而交易者 A 的修改后的市场数据将发布在频道“Trader A, Bond ABC”上,交易者 B 的修改后的市场数据将发布在频道“Trader”上。 B、邦德 ABC”,等等。
One channel per trader per bond: Create one Message Channel per trader, per bond, solely for the modified market data of that bond. For example, the market data for bond ABC would be published on channel "Bond ABC," while the modified market data for trader A would be published on channel "Trader A, Bond ABC," modified market data for trader B on channel "Trader B, Bond ABC," and so on.
每个交易者一个通道
One Channel per Trader
每个交易者每个债券一个通道
One Channel per Bond per Trader
每种方法都有优点和缺点。例如,每个键的方法使用更多的消息通道。在最坏的情况下,消息通道的数量将是债券总数乘以交易者数量。我们可以对将要创建的通道数量设定上限,因为我们知道只有大约 20 个交易者,而且他们对债券的定价绝不会超过几百种。这使得上限低于 10,000 个范围,与市场数据价格馈送使用的近 100,000 个消息通道相比,这并不算奇怪。此外,由于我们使用的是 TIB,并且消息通道非常便宜,因此消息通道不是一个严重的问题。然而,从管理角度来看,消息通道的绝对数量每次添加债券时,必须为每个交易者维护一个通道。这在一个非常动态的系统中可能会很严重。然而,我们的系统本质上是静态的。它还具有用于自动管理消息通道的基础结构。这与使用类似方法的遗留组件的继承架构相结合,最大限度地减少了缺点。这并不是说我们应该创建不必要的过多消息通道。相反,我们可以实现一种使用大量消息通道的架构方法当有理由的时候。
There are advantages and disadvantages to each approach. The per-bond approach, for example, uses a lot more Message Channels. In the worst-case scenario, the number of Message Channels will be the total number of bonds multiplied by the number of traders. We can put upper bounds on the number of channels that will be created, since we know that there are only around 20 traders and they never price more than a couple hundred bonds. This puts the upper limit below the 10,000 range, which is not so outlandish compared to the nearly 100,000 Message Channels the market data price feed is using. Also, since we are using the TIB, and Message Channels are quite inexpensive, the number of Message Channels is not a severe issue. However, the sheer number of Message Channels could be a problem from a management perspective. Every time a bond is added, a channel for each trader must be maintained. This could be severe in a very dynamic system. Our system, however, is essentially static. It also has an infrastructure for automatically managing Message Channels. This combined with the inherited architecture of a legacy component using a similar approach minimizes the downside. This is not to say we should make an unnecessarily excessive number of Message Channels. Rather, we can implement an architectural approach that uses a large number of Message Channels when there is a reason.
这种情况是有原因的,归结为逻辑位置。如果我们实施每个交易者的方法,分析引擎需要逻辑来对输入和输出通道进行分组。这是因为分析引擎的输入通道是针对每个债券的,而输出消息通道是针对每个交易者的,要求分析引擎将来自特定交易者的多个债券的所有分析输入路由到交易者特定的输出消息频道。这有效地将分析引擎转变为基于内容的路由器,为我们的应用程序实现自定义路由逻辑。
And there is a reason in this case, which comes down to the location of logic. If we implement the per-trader approach, the Analytics Engine needs logic to group input and output channels. This is because the input channels from the Analytics Engine are per-bond, and the output Message Channels would be per-trader, requiring the Analytics Engine to route all analytics input from multiple bonds for a particular trader to a trader-specific output Message Channel. This effectively turns the analytics engine into a Content-Based Router to implement custom routing logic for our application.
遵循消息总线结构,分析引擎是一个通用服务器,可以由系统中的其他几个系统使用,因此我们不希望将系统特定的功能置于云中。另一方面,每只债券的方法是有效的,因为交易者拥有债券价格的分析输出的想法是公司接受的做法。每个债券的方法保持了市场数据源的消息通道分离完整,同时添加了更多消息通道。在到达客户端之前,我们需要一个基于内容的路由器将这几个通道组合成可管理数量的通道。我们不希望交易者桌面上运行的客户端应用程序监听数千或数万个消息通道。现在的问题是把基于内容的路由器放在哪里。我们可以简单地让 C++/TIB通道适配器将所有消息转发到单个消息通道上的定价网关。这很糟糕,原因有两个:我们将在 C++ 和 Java 之间拆分业务逻辑,并且我们将失去单独消息通道的好处在 TIB 端,这允许我们避免稍后在数据流中进行过滤。查看我们的 Java 组件,我们可以将其放置在定价网关中,或者在定价网关和客户端之间创建一个中间组件。
Following the Message Bus structure, the Analytics Engine is a generic server that could be used by several other systems in the system, so we don't want to cloud it with system-specific functionality. On the other hand, the per-bond approach works, since the idea of a trader owning the analytics output of bond prices is a company accepted practice. The per-bond approach keeps the Message Channel separation of the market data feed intact while adding several more Message Channels. Before we reach the client, we want a Content-Based Router to combine these several channels into a manageable number of channels. We don't want the client application running on the trader's desktop to be listening to thousands or tens of thousands of Message Channels. Now the question becomes where to put the Content-Based Router. We could simply have the C++/TIB Channel Adapter forward all of the messages to the pricing gateway on a single Message Channel. This is bad for two reasons: We would be splitting up the business logic between C++ and Java, and we would lose the benefit of the separate Message Channels on the TIB side that allows us to avoid filtering later in the data flow. Looking at our Java components, we could either place it in the pricing gateway or create an intermediary component between the pricing gateway and the client.
理论上,如果我们坚持基于债券的消息通道分离一直到客户端,定价网关将使用与定价网关和分析引擎相同的通道结构重新广播定价信息。这意味着 JMS 中所有绑定专用 TIB 通道的复制。即使我们在定价网关和客户端之间创建一个中间组件,定价网关仍然必须复制 JMS 中的所有通道。然而,直接在定价网关中实现逻辑使我们能够避免在 JMS 中复制大量通道,从而允许我们以每个交易者一个的顺序创建数量少得多的通道。定价网关通过 C++/TIB 自行注册通道适配器作为系统中每个交易者的每个债券的消费者。然后,定价网关仅向每个特定客户端转发与该特定交易者相关的消息。这样,我们在JMS 端仅使用少量的,同时最大化 TIB 端分离的好处。
In theory, if we persisted the bond-based separation of Message Channels all the way to the client, the pricing gateway would rebroadcast pricing information with the same channel structure as the pricing gateway and Analytics Engine. This means a duplication of all of the bond-dedicated TIB channels in JMS. Even if we create an intermediary component between the pricing gateway and the client, the pricing gateway will still have to duplicate all of the channels in JMS. However, implementing logic directly in the pricing gateway allows us to avoid duplicating the large number of channels in JMSallowing us to create a much smaller number of channels in the order of one per trader. The pricing gateway registers itself through the C++/TIB Channel Adapter as a consumer for each bond of every trader in the system. Then, the pricing gateway forwards each specific client only the messages related to that particular trader. This way, we use only a small number of Message Channels on the JMS end while maximizing the benefit of the separation on the TIB end.
完整的市场数据流向客户
The Complete Market Data Flow to the Client
消息通道布局讨论是一个很好的例子,说明了集成模式的重要性。目标是弄清楚如何有效地使用消息通道。仅仅说使用模式是不够的。您需要弄清楚如何最好地实施它并将其合并到您的系统中以解决手头的问题。此外,这个例子还展示了商业力量的实际行动。如果我们可以在任何组件中实现业务逻辑,我们就可以采用每个交易者的方法,并通过更少的渠道实现总体上更简单的方法。
The Message Channel layout discussion is a good example of how integrating patterns is important. The goal was to figure out how to effectively use the Message Channels. Saying you use a pattern isn't enough. You need to figure out how to best implement it and incorporate it into your system to solve the problems at hand. Additionally, this example shows business forces in action. If we could implement business logic in any of our components, we could have gone with the per-trader approach and implemented an overall more simple approach with many fewer channels.
现在我们已经了解了 Java/JMS 组件和 C++/TIBCO 组件之间的通信机制,并且了解了一些消息通道结构,接下来我们需要决定 Java 组件应该使用哪种类型的 JMS消息通道来进行通信。在我们可以在不同的消息渠道之间进行选择之前JMS 中可用,让我们看一下系统的高层消息流。我们有两个与客户沟通的网关(定价和贡献)。市场数据从定价网关流向客户端,定价网关将其发送到贡献网关。客户端应用程序向定价网关发送消息以更改应用于每个债券的分析。贡献网关还向客户端应用程序发送消息,将价格更新的状态转发到不同的交易场所。
Now that we know the mechanics of the communication between the Java/JMS components and the C++/ TIBCO components, and we have seen some Message Channel structuring, we need to decide which type of JMS Message Channels the Java components should use to communicate. Before we can choose between the different Message Channels available in JMS, let's look at the high-level message flow of the system. We have two gateways (pricing and contribution) communicating with the client. Market data flows to the client from the pricing gateway, which sends it out to the contribution gateway. The client application sends messages to the pricing gateway to alter the analytics being applied to each bond. The contribution gateway also sends messages to the client application relaying the status of the price updates to the different trading venues.
系统消息流
The System Message Flow
JMS 规范描述了两种消息通道类型:队列(点对点通道)和主题(发布-订阅通道) 。回想一下,使用发布-订阅的情况是让所有感兴趣的消费者都能接收到一条消息,而使用点对点的情况是确保只有一个合格的消费者接收到特定的消息。
The JMS specification describes two Message Channel types: Queue (a Point-to-Point Channel) and Topic (a Publish-Subscribe Channel). Recall that the case for using publish-subscribe is to enable all interested consumers to receive a message, while the case for using point-to-point is to ensure that only one eligible consumer receives a particular message.
许多系统只是将消息广播到所有客户端应用程序,让每个单独的客户端应用程序自行决定是否处理特定消息。这对我们的应用程序不起作用,因为大量的市场数据消息被发送到每个客户端应用程序。如果我们向不感兴趣的交易者广播市场数据更新,我们将不必要地浪费客户端处理器周期来决定是否处理市场数据更新。
Many systems would simply broadcast messages to all client applications, leaving each individual client application to decide for itself whether or not to process a particular message. This will not work for our application, since a large number of market data messages are being sent to each client application. If we broadcast market data updates to uninterested traders, we will be unnecessarily wasting client processor cycles deciding whether or not to process a market data update.
点对点通道最初但业务要求是交易者可以同时登录多台机器。如果交易者同时在两个工作站登录并发送点对点价格更新,则只有两个客户端应用程序之一会收到该消息。这是因为点对点通道上只有一个消费者请注意,只有交易者客户端应用程序每组中的第一个会收到该消息。
Point-to-Point Channels initially sounds like a good choice, since the clients are sending messages to unique servers, and vice versa. But it was a business requirement that traders may be logged in to multiple machines at the same time. If we have a trader logged in at two workstations simultaneously and a point-to-point price update is sent, only one of the two client applications will get the message. This is because only one consumer on a Point-to-Point Channel can receive a particular message (see figure on the next page). Notice that only the first of each group of a trader's client applications receives the message.
价格更新的点对点消息传送
Point-to-Point Messaging for Price Updates
我们可以使用收件人列表来解决这个问题,它将消息发布到预期收件人列表,保证只有收件人列表中的客户端才会收到消息。使用这种模式,系统可以创建收件人列表,其中包含与每个交易者相关的所有客户端应用程序实例。发送与特定交易者相关的消息将依次将该消息发送至收件人列表中的每个应用程序。这保证了与特定交易者相关的所有客户端应用程序实例都会收到该消息。这种方法的缺点是需要大量的实现逻辑来管理接收者和发送消息。
We could solve this using Recipient List, which publishes messages to a list of intended recipients, guaranteeing that only clients in the recipient list will receive messages. Using this pattern, the system could create recipient lists with all client application instances related to each trader. Sending a message related to a particular trader would in turn send the message to each application in the recipient list. This guarantees all client application instances related to a particular trader would receive the message. The downside of this approach is that it requires quite a bit of implementation logic to manage the recipients and dispatch messages.
价格更新的收件人列表
Recipient List for Price Updates
尽管点对点可以工作,但让我们看看是否有更好的方法。使用发布-订阅通道,系统可以在特定于交易者的通道而不是特定于客户端应用程序的通道上广播消息。这样,处理单个交易者消息的所有客户端应用程序都将接收并处理该消息(参见下图)。
Even though point-to-point could be made to work, let's see if there is a better way. Using Publish-Subscribe Channels, the system could broadcast messages on trader-specific channels rather than client applicationspecific channels. This way, all client applications processing messages for a single trader would receive and process the message (see the following figure).
发布-订阅价格更新消息
Publish-Subscribe Messaging for Price Updates
使用发布-订阅通道的缺点是服务器组件不能保证唯一的消息处理。一个服务器组件的多个实例可能会被实例化,并且每个实例都会处理相同的消息,这可能会发送出无效的价格。
The downside of using Publish-Subscribe Channels is that unique message processing is not guaranteed with the server components. It would be possible for multiple instances of a server component to be instantiated and for each instance to process the same message, possibly sending out invalid prices.
回想一下系统消息流,每个消息通道只有一个通信方向是令人满意的。 采用发布-订阅的服务器到客户端通信令人满意,而客户端到服务器通信则不然;采用点对点的客户端到服务器通信令人满意,而服务器到客户端则不令人满意。由于不需要在两个方向上使用相同的消息通道,因此我们可以仅在一个方向上使用每个消息通道。客户端到服务器的通信将采用点对点的方式实现,而服务器到客户端的通信将采用发布-订阅的方式实现。使用消息通道的这种组合,该系统受益于使用点对点消息传递和发布-订阅的多播性质与服务器组件的直接通信,而没有任何缺点。
Recalling the system message flow, only a single communication direction is satisfactory with each Message Channel. Server-to-client communication with publish-subscribe is satisfactory, while client-to-server communication is not, and client-to-server communication with point-to-point is satisfactory, while server-to-client is not. Since there is no need to use the same Message Channel in both directions, we can use each Message Channel in only one direction. Client-to-server communication will be implemented with point-to-point, while server-to-client communication will be implemented with publish-subscribe. Using this combination of Message Channels, the system benefits from direct communication with the server components using point-to-point messaging and the multicast nature of publish-subscribe without either of the drawbacks.
具有通道类型的消息流
Message Flow with Channel Types
模式是工具,模式的集合是工具箱。他们帮助解决问题。有些人认为模式只在设计过程中才有用。按照工具箱的比喻,这就像说工具只在你建造房子时才有用,而在你修理它时则没有用。事实上,如果应用得当,模式在整个项目中都是一个有用的工具。在以下部分中,我们使用上一节中使用的相同模式探索过程来解决我们当前工作系统中的问题。
Patterns are tools, and collections of patterns are toolboxes. They help solve problems. Some think that patterns are only useful during design. Following the toolbox analogy, this is like saying that tools are only useful when you build a house, not when you fix it. The fact is that patterns are a useful tool throughout a project when applied well. In the following sections we use the same pattern exploration process we used in the previous section to solve problems in our now working system.
交易者希望在收到债券的新市场数据时表格单元格会闪烁,清楚地表明变化。Java客户端收到带有新数据的消息,这会触发客户端数据缓存更新并最终在表中闪烁。问题是更新非常频繁。GUI 线程堆栈变得过载并最终冻结客户端,因为它无法响应用户交互。我们将假设刷新已被优化,并通过更新过程专注于消息的数据流。对性能数据的检查显示客户端应用程序每秒接收多个更新;两次更新的间隔时间可以不到一毫秒。聚合器似乎可以帮助减慢消息流的两种模式和消息过滤器。
Traders want table cells to flash when new market data is received for a bond, clearly indicating changes. The Java client receives messages with new data, which triggers a client data cache update and eventually flashing in the table. The problem is that updates come quite frequently. The GUI thread stack is becoming overloaded and eventually freezing the client, since it can't respond to user interaction. We will assume that the flashing is optimized and concentrate on the data flow of messages through the updating process. An examination of performance data shows the client application is receiving several updates a second; two updates can occur less than a millisecond apart. Two patterns that seem like they could help slow down the message flow are Aggregator and Message Filter.
第一个想法是实现消息过滤器,通过丢弃参考消息后短时间内收到的更新来控制消息流的速度。举个例子,假设我们要忽略 5 毫秒内的消息。消息过滤器可以缓存最后可接受的消息的时间,并丢弃在接下来的 5 毫秒内收到的任何消息。虽然其他应用程序可能无法承受这种程度的数据丢失,但由于价格更新的频率,这在我们的系统中是完全可以接受的。
A first thought is to implement a Message Filter to control the speed of the message flow by throwing out updates received a short time after the reference message. As an example, let's say that we are going to ignore messages within 5 milliseconds of each other. The Message Filter could cache the time of the last acceptable message and throw out anything received within the next 5 milliseconds. While other applications may not be able to withstand data loss to such an extent, this is perfectly acceptable in our system due to the frequency of price updates.
基于时间的消息过滤器
Time-Based Message Filter
这种方法的问题在于并非所有数据字段都会同时更新。每个债券都有大约 50 个向用户显示的数据字段,包括价格。我们意识到并非每条消息中的每个字段都会更新。如果系统忽略连续的消息,它很可能会丢弃重要的数据。
The problem with this approach is that not all data fields are updated at the same time. Each bond has approximately 50 data fields displayed to the user, including price. We realize that not every field is updated in every message. If the system ignores consecutive messages, it may very well be throwing out important data.
另一个令人感兴趣的模式是聚合器。聚合器用于管理将多个相关消息协调为单个消息,从而可能减少消息流。聚合器可以保留第一个聚合消息中的绑定数据的副本,然后仅更新连续消息中的新字段或更改字段。最终,聚合的债券数据将通过消息传递给客户端。现在,我们假设聚合器将像消息。稍后,我们将探索另一种选择。
The other pattern of interest is the Aggregator. The Aggregator is used to manage the reconciliation of multiple, related messages into a single message, potentially reducing the message flow. The Aggregator could keep a copy of the bond data from the first aggregated message, then update only new or changed fields from successive messages. Eventually, the aggregated bond data will be passed in a message to the client. For now, let's assume that the Aggregator will send a message every 5 milliseconds like the Message Filter. Later, we'll explore another alternative.
部分连续更新的聚合器
Aggregator with Partial Successive Updates
与任何其他模式一样并不是灵丹妙药。它有其优点和缺点,需要探索。一个潜在的缺点是实施聚合器在我们的例子中,只有当许多消息在相对较短的时间内传入同一键时,才会大大减少消息流量。然而,如果 Java 客户端仅收到所有交易者债券的一个字段的更新,我们将一事无成。例如,如果我们在指定时间范围内收到 1,000 条具有 4 个利息债券的消息,我们将在该时间范围内将消息流从 1,000 条减少到 4 条。或者,如果我们在同一时间范围内收到 1,000 条消息和 750 个利息债券,我们会将消息流从 1,000 条减少到 750 条:与付出的努力相比,收获相对较小。对消息更新的快速分析证明,Java 客户端接收到许多消息更新同一键的字段,因此也是相关消息。所以,聚合器实际上
The Aggregator, like any other pattern, is not a silver bullet; it has its pluses and minuses that need to be explored. One potential minus is that implementing an Aggregator would reduce the message traffic by a great amount in our case only if many messages are coming in within a relatively short time regarding the same bond. However, we would accomplish nothing if the Java client receives updates for only one field across all of the traders' bonds. For example, if we receive 1,000 messages in a specified timeframe with four bonds of interest, we would reduce the message flow from 1,000 to four messages over that timeframe. Alternatively, if we receive 1,000 messages in the same timeframe with 750 bonds of interest, we will have reduced the message flow from 1,000 to 750 messages: relatively little gain for the amount of effort. A quick analysis of the message updates proves that the Java client receives many messages updating fields of the same bondand therefore related messages. So, Aggregator is in fact a good decision.
剩下的就是确定聚合器如何知道何时发送它聚合的消息。该模式描述了聚合器了解何时发送消息的一些算法。其中包括使聚合器在经过一定时间后、在数据集中的所有必填字段完成后发送其内容的算法等。所有这些方法的问题在于,控制消息流的是聚合器(而不是客户端),并且客户端(而不是消息流)是这种情况下的主要瓶颈。
What's left is to determine how the Aggregator will know when to send a message it has been aggregating. The pattern describes a few algorithms for the Aggregator to know when to send the message. These include algorithms to cause the Aggregator to send out its contents after a certain amount of time has elapsed, after all required fields in a data set have been completed, and others. The problem with all of these approaches is that the Aggregator, not the client, is controlling the message flowand the client, not the message flow, is the major bottleneck in this case.
这是因为聚合器假设其清除消息的使用者(在本例中为客户端应用程序)是事件驱动的使用者,或者依赖于外部源事件的使用者。我们需要将客户端变成轮询消费者,或者连续检查消息的消费者,因此客户端应用程序可以控制消息流。我们可以通过创建一个后台线程来实现这一点,该线程不断循环遍历键集并更新并闪烁自上次迭代以来发生的任何更改。通过这种方式,客户端可以控制何时接收消息,从而保证在高更新期间永远不会出现消息过载的情况。我们可以通过向聚合器发送命令消息并启动更新来轻松实现这一点。聚合器将使用包含客户端将处理的更新字段集的文档消息进行响应。
This is because the Aggregator is assuming the consumers of its purged messages (the client application in this case) are Event-Driven Consumers, or consumers that rely on events from an external source. We need to turn the client into a Polling Consumer, or a consumer that continuously checks for messages, so the client application can control the message flow. We can do this by creating a background thread that continuously cycles through the set of bonds and updates and flashes any changes that have occurred since the last iteration. This way, the client controls when messages are received, and as a result, guarantees that it will never become overloaded with messages during high update periods. We can easily implement this by sending a Command Message to the Aggregator, initiating an update. The Aggregator will respond with a Document Message containing the set of updated fields that the client will process.
选择聚合器而不是消息过滤器显然完全基于我们系统的业务需求的决定。每一个都可以帮助我们解决性能问题,但是使用消息过滤器会以牺牲系统数据完整性为代价来解决问题。
The choice of Aggregator over Message Filter is clearly a decision based solely on the business requirements of our system. Each could help us solve our performance problems, but using the Message Filter would solve the problem at cost of the system data integrity.
闪光灯性能确定后,我们现已投入生产。有一天,整个系统瘫痪了。MQSeries 崩溃,导致多个组件崩溃。我们与这个问题纠缠了一段时间,最后追溯到 MQSeries 死信队列(死信通道的一种实现)。队列变得如此之大以至于导致整个服务器瘫痪。经过探索死信队列中的消息,我们发现它们都是过期的市场数据消息。这是由“缓慢的消费者”或处理消息的速度不够快的消费者引起的。当消息等待处理时,它们会超时(请参阅消息过期模式)并被发送到死信通道。死信队列中过期的市场数据消息数量过多清楚地表明消息流太大,消息在目标应用程序可以使用它们之前就过期了。我们需要修复消息流,并求助于模式来减缓消息流。
With the performance of the flashing fixed, we are now in production. One day, the entire system goes down. MQSeries crashes, bringing several components down with it. We struggle with the problem for a while and finally trace it back to the MQSeries dead letter queue (an implementation of the Dead Letter Channel). The queue grows so large that it brings down the entire server. After exploring the messages in the dead letter queue, we find they are all expired market data messages. This is caused by "slow consumers," or consumers that do not process messages fast enough. While messages are waiting to be processed, they time out (see the Message Expiration pattern) and are sent to the Dead Letter Channel. The excessive number of expired market data messages in the dead letter queue is a clear indication that the message flow is too greatmessages expire before the target application can consume them. We need to fix the message flow, and we turn to patterns for help in slowing down the message flow.
瓶颈
The Bottleneck
合理的第一步是探索使用聚合器解决这个问题,因为我们最近使用这种模式来解决类似的闪烁市场数据控制率问题。系统设计依靠客户端应用程序将市场数据更新消息立即转发到交易场所。这意味着系统迫不及待地收集消息并聚合它们,因此必须放弃 Aggregator。
A reasonable first step is to explore solving this problem with the Aggregator, as we recently used this pattern to solve the similar flashing market datacontrol rate problem. The system design relies on the client application to immediately forward market data update messages to the trading venues. This means the system cannot wait to collect messages and aggregate them, so the Aggregator must be abandoned.
还有另外两种模式可以处理并发消费消息的问题:竞争消费者和消息调度程序。从竞争消费者开始,这种模式的好处是传入消息的并行处理。这是通过使用同一通道上的多个消费者来完成的。只有一个消费者处理每条传入消息,让其他消费者处理连续的消息。然而,竞争消费者对我们不起作用,因为我们在服务器到客户端的通信中使用发布-订阅通道。发布-订阅通道上的竞争消费者意味着所有消费者处理相同的传入消息。这会导致更多的工作却没有任何收获,并且完全达不到该模式的目标。这种做法也必须被放弃。
There are two other patterns that deal with the problem of consuming messages concurrently: Competing Consumers and Message Dispatcher. Starting with Competing Consumers, the benefit of this pattern is the parallel processing of incoming messages. This is accomplished using several consumers on the same channel. Only one consumer processes each incoming message, leaving the others to process successive messages. Competing Consumers, however, will not work for us, since we are using Publish-Subscribe Channels in server-to-client communication. Competing Consumers on a Publish-Subscribe Channel means that all consumers process the same incoming message. This results in more work without any gain and completely misses the goal of the pattern. This approach also has to be abandoned.
消息调度程序描述了一种将多个执行者添加到池中的方法。每个执行者都可以运行自己的执行线程。一个主要消费者侦听消息通道并将消息委托给池中未占用的执行者,然后立即返回侦听通道。这实现了竞争消费者的并行处理优势,但适用于发布-订阅通道。
The Message Dispatcher describes an approach whereby you add several performers to a pool. Each performer can run its own execution thread. One main consumer listens to the Message Channel and delegates the message on to an unoccupied performer in the pool, then immediately returns to listening on the channel. This achieves the parallel processing benefit of Competing Consumers but works on Publish-Subscribe Channels.
上下文中的消息调度程序
The Message Dispatcher in Context
在我们的系统中实现这一点很简单。我们创建一个名为Dispatcher的MessageListener ,其中包含名为Performers的集合。 当调用Dispatcher的onMessage方法时,它会依次从集合来实际处理消息。结果是一个始终立即返回的消息侦听器( Dispatcher) 。这保证了消息处理的稳定流,而不管消息流速率如何。这在发布-订阅通道上的效果与在发布-订阅通道上的效果一样好。点对点通道。有了这个基础设施,客户端应用程序几乎可以以任何速率接收消息。如果客户端应用程序在收到消息后处理消息的速度仍然很慢,则客户端应用程序可以处理延迟处理和可能过时的市场数据,而不是处理 JMS 消息通道中过期的消息。
Implementing this in our system is simple. We create a single MessageListener called the Dispatcher, which contains a collection of other MessageListeners called Performers. When the onMessage method of the Dispatcher is called, it in turn picks a Performer out of the collection to actually process the message. The result is a message listener (the Dispatcher) that always returns immediately. This guarantees a steady flow of message processing regardless of the message flow rate. This works equally as well on a Publish-Subscribe Channel as it does on a Point-to-Point Channel. With this infrastructure, messages can be received by the client application at almost any rate. If the client application is still slow to process messages after receiving them, the client application can deal with the delayed processing and potentially outdated market data rather than the messages expiring in the JMS Message Channel.
本节讨论的崩溃和使用消息调度程序的修复是应用模式限制的一个很好的例子。我们遇到了基于设计缺陷的性能问题,该缺陷阻止客户端并行处理消息。应用模式大大减少了问题,但并没有完全解决它,因为真正的问题是客户端成为瓶颈。这不可能用一千种模式来解决。后来我们通过重构消息流架构来将消息直接从定价网关路由到贡献网关来解决这个问题。因此,模式可以帮助设计和维护系统,但它们不一定能弥补糟糕的前期设计。
The crash discussed in this section and the fix using the Message Dispatcher are an excellent example of the limits of applying patterns. We encountered a performance problem based on a design flaw that prevented the client from processing messages in parallel. Applying patterns greatly reduced the problem but did not completely fix it, because the real problem was the client becoming a bottleneck. This couldn't be fixed with a thousand patterns. We later addressed this problem by refactoring the message flow architecture to route messages directly from the pricing gateway to the contribution gateway. So, patterns can help design and maintain a system, but they don't necessarily make up for poor upfront design.
在本章中,我们将模式应用于债券交易系统的几个不同方面,包括解决最初的前期设计问题以及使用模式修复几乎威胁工作的生产崩溃。我们还看到了这些模式,因为它们已经存在于第三方产品、遗留组件以及我们的 JMS 和 TIBCO 消息传递系统中。最重要的是,这些都是我们在设计和维护自己的系统时遇到的相同类型的架构、技术和业务问题的实际问题。希望阅读有关将模式应用于该系统的内容能让您更好地理解这些模式以及如何将它们应用到您自己的系统中。
Throughout this chapter, we have applied patterns to several different aspects of a bond trading system, including solving initial upfront design problems and fixing a nearly job-threatening production crash with patterns. We also saw these patterns as they already exist in third-party products, legacy components, and our JMS and TIBCO messaging systems. Most importantly, these are real problems with the same types of architectural, technical, and business problems we experience as we design and maintain our own systems. Hopefully, reading about applying patterns to this system has given you a better understanding of the patterns and how to apply them to your own systems.
通过肖恩·内维尔
by Sean Neville
随着数据通过消息传递管道跨系统和域边界流动,并且随着开发人员和架构师变得更加精通管理消息传递系统的模式,新的标准和产品将会出现,以扩展这些模式的战术范围。随着时间的推移,模式往往会加强,但在其他方面变化甚微(如果有的话)。但它们的实施策略通常会迅速发展,以允许开发人员将它们应用到更广泛的复杂范围。基本信息例如,随着实现工件从电子数据交换 (EDI) 发展到专有的面向消息的中间件 (MOM)、开放 XML 和基于 SOAP 的 Web 服务、全球业务流程执行语言 (BPEL) 和超过。
As data flows across system and domain boundaries through messaging conduits, and as developers and architects become more proficient in the patterns that govern messaging systems, new standards and products will emerge to extend the tactical reach of those patterns. Over time, patterns tend to strengthen but otherwise change little, if at all; but their implementation strategies often evolve rapidly to allow developers to apply them to much broader scales of sophistication. The fundamental Message pattern, for example, finds its reach and applicability extended as implementation artifacts grow from Electronic Data Interchange (EDI) to proprietary Message-Oriented Middleware (MOM) to open XML and SOAP-based Web services to global Business Process Execution Language (BPEL) and beyond.
本章根据应用程序开发人员在 2000 年代中期将遇到的新兴标准来展望基于消息的企业集成的未来。其中许多标准目前尚未普遍使用,但正在与广泛的行业支持相结合,并且可能成为集成模式实现的基础,特别是在面向服务的体系结构中。其中大多数扩展了新兴的 Web 服务技术来支持本书中介绍的模式类型。我们研究为什么这些标准对于设计模式很重要,哪些组织正在创建它们以及他们是如何进行的,
This chapter takes a look at the future of message-based enterprise integration in terms of the emerging standards that application developers will encounter in the mid-2000s. Many of these standards are not currently in common use but are coalescing with broad industry support and are likely to serve as the foundation of integration pattern implementations, particularly in service-oriented architectures. Most of them extend nascent Web services technologies to support the types of patterns presented in this book. We look at why these standards are important in relation to design patterns, which organizations are creating them and how they're going about it, and offer a brief summary of the technical solutions for business process integration that a few of the most promising Web services and Java standards aim to provide.
两种思维产物提供了当今软件架构中最高级别的抽象:面向编程,包括面向对象编程、面向服务编程、生成式编程等;和模式语言,如本书中记录的那样。如果一个特定的方向或设计模式被证明是有用的,并且如果它的上下文被证明经常重复出现,那么用于实现该模式的策略和策略通常会变得非常相似。多个平台、产品和应用程序上的模式解决方案最终拥有很少的差异,但这些微小的差异通常是令人沮丧的增长抑制剂,通常是语义性的,并产生阻碍规模复杂性的互操作性。
Two mental artifacts provide the highest levels of abstraction in software architecture today: programming orientations, which include object-oriented programming, service-oriented programming, generative programming, and the like; and pattern languages, such as that documented in this book. If a particular orientation or design pattern proves useful, and if its context proves to recur frequently, the tactics and strategies used to implement the pattern often become very similar. Pattern solutions on multiple platforms, products, and applications end up owning very few differencesbut those few differences are often frustrating growth inhibitors, typically semantic in nature, and produce interoperability that hinders sophistication of scale. To extend the reach of applications and the patterns on which they're based, such differences tend to be eliminated from products and platforms through formally agreed-upon standards.
当模式的策略或实现策略被标准化时,该模式的实用性并不会减弱,而是会被该标准所取代;相反,情况恰恰相反。就像树干内的年轮一样,强大的模式会自行发展到更广泛的适用性水平,在更广泛的背景实例中创建并应用自己。这种规模的增长是因为模式实现的不同实例可以彼此互操作。以典型的面向消息的J2EE应用程序为例,管道和过滤器等模式存在于开发人员设计的应用程序内的多个级别,以及用于托管应用程序的服务器产品内、该服务器内的容器和服务内、构成消息传递子系统的过滤器和组件内,等等以递归方式存在。
When a pattern's tactic or implementation strategy is standardized, the pattern does not wane in usefulness, replaced by that standard; instead, quite the opposite occurs. Like rings within a tree trunk, a strong pattern grows upon itself to ever-broader levels of applicability, creating and then applying itself in broader instances of context. This growth in scale occurs because separate instances of a pattern's implementation become interoperable with one another. Taking a typical message-oriented J2EE application as an example, a pattern such as Pipes and Filters exists at many levelswithin the application as designed by the developer and also within the server product used to host the application, within the containers and services within that server, within the filters and components that compose the messaging subsystems, and so forth in recursive fashion.
新兴的消息传递和 Web 服务标准将通过扩展使用它们的开发人员的范围来增强本书中的模式。BPEL 和 Web 服务可靠性 (WS-Reliability) 等标准将这些模式的范围扩展到比特、语言、产品之外,并向外传播到以人类和系统为中心的用例和需求圈中越来越有用的组合。应用程序开发人员可以停止使用相关标识符等模式将原始消息回复与其发送者相匹配,并可以开始使用相同的模式来关联由多个异步消息发送者和接收者组成的流程组件。如果没有消息传递标准的好处,Java 开发人员可能会在代码级别实现消息路由器来检查各个 XML 消息的内容,以便以编程方式将消息转发到特定服务或过滤器。新兴的工作流程和编排标准将允许同一开发人员应用他或她的消息路由器知识模式在更高的抽象级别上在流程组合之间路由工作流,其中战术工件是业务流程组件而不是 XML 文档的原始部分。换句话说,应用程序开发人员可以停止花费宝贵的时间链接协议,并着手跨域边界链接业务流程和服务。
Emerging messaging and Web service standards will serve to strengthen the patterns in this book by extending the reach of the developers who employ them. Standards such as BPEL and Web Services Reliability (WS-Reliability) propagate the scope of these patterns beyond bits, beyond languages, beyond products, and outward into increasingly useful compositions in the human- and system-centric circles of use cases and requirements. Application developers can stop using patterns such as Correlation Identifier to match raw message replies to their senders and can start using that same pattern to correlate process components that consist of multiple asynchronous message senders and receivers. Without the benefit of messaging standards, a Java developer may implement Message Router at the code level to inspect the contents of individual XML messages in order to programmatically forward the message to a particular service or filter. Emerging workflow and choreography standards will allow that same developer to apply his or her knowledge of the Message Router pattern at a higher level of abstraction to route workflow among process compositions, where the tactical artifacts are business process components rather than raw sections of XML documents. In other words, application developers can stop spending precious time linking protocols and set about the business of linking business processes and services across domain boundaries.
设计模式和编程方向对于开发复杂系统的有用性与应用程序开发人员依赖通用的、可互操作的实施策略和战术的能力成正比。如今,在 J2EE 中使用消息传递模式的策略可能意味着使用 JMS、JCA 或 JAX-RPC;明天,应用程序开发人员使用的策略可能涉及使用模型驱动的技术、基于模式的脚本、方面或意图,并且如果适当地接受了模式的上下文定义和相关标准,则实现策略中的任何此类转变只会增加模式的重要性。
The usefulness of design patterns and programming orientation for developing sophisticated systems is proportional to the application developer's ability to depend upon common, interoperable implementation strategies and tactics. Today, strategies for working with a messaging pattern in J2EE may mean use of JMS, JCA, or JAX-RPC; tomorrow, the strategies used by application developers may involve use of model-driven technology, schema-based scripts, aspects, or intentionsand any such shifts in implementation tactics only increase the significance of the patterns if the pattern's contextual definition and relevant standards are appropriately embraced. Standards provide the best way we currently know to enforce these commonalities to extend the software architect's mastery of design patterns.
与流行的愤世嫉俗相反,标准并不是在与实际应用程序开发隔离的供应商真空中开发的。它们旨在统一和改进应用程序开发人员发现和开发的实现方法。通过练习本书中的设计模式,即使您不直接为规范工作组做出贡献,也可能会为标准的开发做出贡献。监督规范工作组的组织和联盟并不总是在识别和同化实施方法方面做得很好,但这就是目的。
Contrary to popular cynicism, standards are not intended to be developed in a vendor vacuum isolated from practical application development. They are intended to unify and improve the implementation approaches discovered and developed by application developers. By exercising the design patterns in this book, you may be contributing to the development of a standard even if you don't directly contribute to a specification working group. The organizations and consortiums that oversee specification working groups don't always do a great job of recognizing and assimilating implementation approaches, but that's the intention.
当一个发明者或一组发明者向标准机构提出正式提案时,标准就正式诞生。通常,这需要成为标准机构的成员。每个标准机构都执行自己的流程,将提案形成标准,所有成员都受到这些规则的法律约束。这些过程通常涉及组建一个工作组或委员会,以在组织管理层的监督和最终批准下进一步制定规范。知识产权和许可政策通常是这些组织中的热门话题,并且在标准机构之间甚至标准机构内的工作组之间可能会有所不同。
Standards are officially born when an inventor or group of inventors makes a formal proposal to a standards body. Usually, this requires membership in the standards body. Each standards body enforces its own process for shaping proposals into a standard, and all members are legally bound to those rules. The processes generally involve the forming of a working group or committee to further develop the specification under the oversight and eventual approval of the organization's management. Intellectual property rights and licensing policies are often hot topics in these organizations and can vary between standards bodies and even between working groups within the standards bodies.
以下是参与创建新兴消息传递和 Web 服务标准的主要标准组织:
Here's a look at the major standards organizations involved in creating the emerging messaging and Web service standards:
W3C:万维网联盟( http://www.w3c.org )开发了许多基本的 Web 技术,这些技术又被其他标准组织用作构建块。它由一个由研究人员和工程师组成的国际团队管理,并由一大批成员供应商、内容提供商、政府、研究实验室和其他实体组成。W3C 在工作组中以及随后的公开中利用开放的协作审查流程,从而导致冗长但总体质量较高的迭代。通过 W3C 创建的技术通常不受成员知识产权主张的阻碍。W3C 技术包括 SOAP 和 WSDL 以及所有核心 XML 规范。
OASIS:以 W3C 技术为基础的几个组织之一是结构化信息标准促进组织 (Organization for the Advancement of Structured Information Standards),即 OASIS ( http://www.oasis-open.org )。这个非营利供应商联盟旨在促进 SGML 开发指南,现在致力于推动全球电子商务标准的采用。OASIS 技术委员会正在制定许多新兴的 Web 服务标准,例如 ebXML 和 WS-Reliability。OASIS 还托管xml.org门户。
WS-I:Web 服务互操作性组织 (WS-I) ( http://www.ws-i.org )旨在确保 Web 服务技术和标准适合以通用、可互操作的方式实现业务协作。该组织推广可跨多个系统、平台和语言应用的 Web 服务协议和实践。实现这些目标的一个关键策略是 WS-I Basic Profile,它在第一个配置文件中指定了 Web 服务标准及其版本号的集合,这些标准是 XML Schema 1.0、SOAP 1.1、WSDL 1.1 和 UDDI 1.0 以及约定规定它们应该如何一起使用。因此,WS-I 打算发挥统一作用,确保参与 Web 服务开发的各个供应商能够以有利于应用程序开发人员的方式协同工作。
JCP:Java Community Process (JCP) ( http://www.jcp.org )为其他组织开发的 Web 服务和消息传递标准等技术生成 Java 语言绑定和 J2EE API。由 Sun Microsystems 领导的 JCP 历史上并不是一个开放的过程;尽管它使用与其他标准组织类似的专家组和提案流程,但知识产权通常由 Sun 保留,并有偿许可给 Java 和 J2EE 平台供应商。大多数 JSR 专家组也由 Sun 工程师领导。虽然缺乏其他组织的开放性,但 JCP 受益于 Sun 提供的关注,在某种程度上不受外部议程的阻碍。
特设供应商联盟:在建立对部分新兴 Web 服务技术的控制权的竞赛中,特别是为了企业集成的目的,IBM 和 Microsoft 等传统竞争对手在某些情况下联合起来发布了未来的标准,而不将这些工作提交给任何标准机构。许多 WS-* 规范都属于这一类。通常,通过供应商联盟制定的标准最终会提交给标准组织;例如,前景光明的 BPEL 在提交给 OASIS 之前,就曾是 Microsoft 和 IBM 的创造。对保留知识产权的兴趣似乎是临时工作的催化剂。这些规范无需许可开放即可实现互操作性,
W3C: The World Wide Web Consortium (http://www.w3c.org) develops many of the basic Web technologies that are in turn used as building blocks by other standards organizations. It is managed by an international team of researchers and engineers, and consists of a large group of member vendors, content providers, governments, research laboratories, and other entities. The W3C makes use of an open, collaborative review process in working groups and subsequently in public that results in lengthy but generally high-quality iterations. Technologies created through the W3C are usually unencumbered by member claims to intellectual property rights. W3C technologies include SOAP and WSDL as well as all of the core XML specifications. The W3C Choreography Working Group will likely resolve conflicting business process specifications and provide the definitive basis for integration using Web services.
OASIS: One of several organizations that builds on W3C technologies is the Organization for the Advancement of Structured Information Standards, or OASIS (http://www.oasis-open.org). Chartered to foster guidelines for SGML development, this nonprofit consortium of vendors now focuses on driving the adoption of global e-business standards. OASIS technical committees are producing many emerging Web service standards, such as ebXML and WS-Reliability. OASIS also hosts the xml.org portal.
WS-I: The Web Services Interoperability Organization (WS-I) (http://www.ws-i.org) aims to ensure that Web service technologies and standards are suitable for enabling business collaborations in a generic, interoperable manner. The organization promotes Web service protocols and practices that can apply across multiple systems, platforms, and languages. A key tactic in achieving these goals is the WS-I Basic Profile, which specifies the collection of Web service standards along with their version numbersin the first profile these are XML Schema 1.0, SOAP 1.1, WSDL 1.1, and UDDI 1.0as well as conventions governing how they should be used together. Thus, the WS-I intends to play a unifying role in ensuring that various vendors involved in Web services development can work together in a way that benefits application developers. It was founded and is managed by the leading Web services vendor companies, including Microsoft, IBM, BEA Systems, Oracle, and others.
JCP: The Java Community Process (JCP) (http://www.jcp.org) produces Java language bindings and J2EE APIs for technologies such as the Web services and messaging standards developed by other organizations. Led by Sun Microsystems, the JCP has not historically been an open process; although it makes use of expert groups and a proposal process similar to that of other standards organizations, intellectual property rights are typically retained by Sun and licensed to the Java and J2EE platform vendors for a fee. Most of the JSR expert groups are also led by Sun engineers. While lacking the openness of other organizations, the JCP has benefited from the focus that Sun has provided, somewhat unhindered by external agendas.
Ad Hoc Vendor Consortiums: In the race to establish control over portions of the emerging Web services technologies, particularly for the purposes of enterprise integration, traditional competitors such as IBM and Microsoft have in some cases united to publish would-be standards without submitting those works to any standards body. Many of the WS-* specifications fall into this bucket. Often, standards developed through vendor consortiums end up as submissions to standards organizations; the promising BPEL, for example, spent its early life as a creation of Microsoft and IBM before being submitted to OASIS. Interest in retaining intellectual property rights seems to be the catalyst for working in ad hoc fashion. These specifications achieve interoperability without the licensing openness, which is often satisfactory for application developers integrating platforms as popular as those produced by Microsoft and IBM, though whether these works are truly "open standards" is understandably the subject of much debate.
亚里士多德不仅满足于影响面向对象的编程、结构分解和领域建模,还顽皮地提出了困扰软件架构的最具影响力的反问之一:世界主要是一系列过程,还是一系列对象?
Not content merely to influence object-oriented programming, structural decomposition, and domain modeling, Aristotle also mischievously posed one of the most influential rhetorical questions ever to trouble software architecture: Is the world predominantly a series of processes, or is it a series of objects?
消息传递标准给出了一个禅宗弟子的答案:是的。世界是一系列对象,其最重要的特征通常是它们与其他对象交互和关联的过程。事实证明,最重要的往往是一个对象与其他对象相关的行为,而不是它自己的内部组成。在 Web 服务和企业集成领域,这种对象/流程混合视图称为业务流程组件。业务流程组件将一系列服务联合成一个逻辑单元,该逻辑单元通过消息传递与其他此类单元交互,以实现高度可扩展、有弹性的逻辑和数据流。过程、对象的结合,
Messaging standards reply with an answer worthy of a Zen disciple: yes. The world is a series of objects whose most important characteristics are usually the processes through which they engage with and relate to other objects. It is often an object's behaviors related to other objects rather than its own internal composition that turn out to be most significant. In the realm of Web services and enterprise integration, this object/process hybrid view is referred to as the business process component. The business process component unites a series of services into a logical unit that interacts with other such units via messaging to achieve highly scalable, resilient flows of logic and data. The coalescence of process, object, and interaction patterns into a business process component is the future of messaging as seen by many Web services vendors and standards bodies.
流程组件是与企业集成特别相关的一组服务的宏观视图。举个非技术性的例子,人可以驾驶汽车,而人和汽车都是独立的、高度复杂的系统;然而,人类和汽车的过程视图将它们视为由彼此相互作用定义的单个组件,并且不关注与它们之间的相互作用分开的人类或汽车的组成。此外,流程组件视图描述了该单个组件与道路上其他组件的交互,同样对其他汽车或驾驶员的内部组成不感兴趣。这与简单地呈现外部接口不同,
The process component is a macro view of a set of services that is particularly relevant to enterprise integration. Taking a nontechnical example, a human can drive a car, and both the human and the car are separate, highly-complex systems; the process view of the human and car, however, sees them as a single component defined by their interactions with one another and does not focus on the composition of either the human or the car separate from the interaction between them. Further, the process component view describes the interactions of that single component with others on the road, again without interest in the internal composition of other automobiles or drivers. This is not the same as simply presenting an external interface, as it also includes the rules that govern behaviors between usages of the interfaces.
标准中出现的业务流程组件是一个单一组件,其内部由一组 Web 服务以及消息如何流入和流出这些服务的定义组成。Web 服务和其他消息目标是用于组成更大组合的构建块,这些组合的交互遵循消息传递模式,流程组件的组合以及多个流程组件到应用程序的链接都遵循消息传递模式。业务流程标准解决 Web 服务之间的消息关联问题,以便创建执行流并包括与错误、事务和数据交换相关的行为。
The business process component, as emerging in standards, is a single component that internally consists of a set of Web services and a definition for how messages flow into and out of them. Web services and other message destinations are building blocks used to compose larger compositions whose interactions follow messaging patternsboth the composition of the process component and the linking of multiple process components into an application follow messaging patterns. Business process standards address the correlation of messages between Web services in order to create flows of execution and include behaviors related to errors, transactions, and data exchange.
下图展示了一个更具技术性的业务示例,其中采购订单的处理由包含多个服务操作的组件来处理。
A more technical business example is illustrated in the following figure in which the processing of a purchase order is handled by a component that consists of a number of service operations.
代表多个内部编排的 Web 服务公开单个目标端点的业务流程组件
The Business Process Component Exposing a Single Destination Endpoint on Behalf of Multiple Internally Choreographed Web Services
该示例包括以下特征和活动:
The example includes the following characteristics and activities:
处理采购订单组件中的每个操作都是在Web 服务的portType上可用的操作,并且公开这些操作的各种 Web 服务可能驻留在合作伙伴生产设施的远程主机上。
Each operation in the Process Purchase Order component is an operation available on the portType of a Web service, and the various Web services that expose the operations may reside on remote hosts in partner production facilities.
当收到采购订单时,会同时调用四个操作,但其中一些操作具有必须同步解决的依赖关系(图中的虚线箭头表示依赖关系)。
When a purchase order is received, four operations are invoked concurrently, but some of the operations have dependencies that must be synchronously resolved (the dashed arrows in the illustration indicate dependencies).
两个特定的依赖关系表明,为了计算最终成本,需要运输协议和保险费用,并且在制造商将资源投入生产计划之前必须收到具有约束力的保险协议。
Two specific dependencies show that the shipping agreement and insurance costs are needed in order to calculate the final cost and that a binding insurance agreement must be received before the manufacturer will commit resources to a production schedule.
所有服务操作异步完成后,采购订单就已得到处理。流程组件不要求消息传递客户端单独与所有这些服务进行交互并管理它们之间的依赖关系,也不要求客户端创建流程管理器实现,而是代表所有服务公开单个网络端点并管理所有服务。内部服务之间的消息传递和依赖关系,简化了客户端的任务。
Once all service operations have asynchronously completed, the purchase order has been processed. Rather than requiring the messaging client to interact with all of these services individually and manage the dependencies between themthat is, rather than requiring the client to create a Process Manager implementationthe process component exposes a single network endpoint on behalf of all the services and manages all of the messaging and dependencies between services internally, simplifying the client's tasks.
采购订单流程组件又链接到另一个业务流程组件,即流程发票组件。
The Purchase Order process component is in turn linked to another business process component, the Process Invoice Component.
四个标准倡议保证了业务流程组件和集成空间的良好外观:ebXML 倡议、称为业务流程执行语言和 Web 服务编排接口的两个竞争提案,以及通过WS-前缀连接的单个规范的集合,这些规范解决了相同的功能,但形式更窄、更具体。
Four standards initiatives warrant a good look in the business process component and integration space: the ebXML initiative, two competing proposals called Business Process Execution Language and Web Services Choreography Interface, and a collection of individual specifications joined through the WS-prefix that address pieces of the same functionality in slightly narrower, more specific fashion.
甚至在 SOAP 和 WSDL 爆炸性流行之前,许多从事业务协作和 B2B 集成项目的聪明的思想家就看到需要开发一个开放、安全和可互操作的基础架构,以便使用 XML 消息传递交换业务信息。在电子商务下使用可扩展标记语言 (ebXML) 旗帜开发的几个规范和计划满足了这一需求,并且随着 SOAP 等技术的流行,ebXML 已经发展到包含这些技术并以此为基础。ebXML 计划由 OASIS 和联合国 UN/CEFACT 组织共同管理。UN/CEFACT 是将 EDI 标准引入世界的全球组织,ebXML 在许多方面代表了 EDI 发展的下一个逻辑阶段。
Even before the explosive popularity of SOAP and WSDL, many bright thinkers working on business collaboration and B2B integration projects saw a need to develop an open, secure, and interoperable infrastructure for exchanging business information using XML messaging. The several specifications and initiatives developed under the Electronic Business using eXtensible Markup Language (ebXML) banner address that need, and as technologies such as SOAP have become popular, ebXML has grown to include and build upon them. The ebXML initiatives are jointly managed through OASIS and the United Nations UN/CEFACT organization. UN/CEFACT is the global group that brought the EDI standard into the world, and ebXML in many ways represents the next logical stage in the evolution of EDI.
ebXML 包含多个规范,涵盖的主题包括企业如何宣传其业务流程并搜索潜在合作伙伴的业务流程、合作伙伴如何达成一致并发起通信、使用注册表来促进业务对话的发现和初始化以及业务流程的行为。促进对话所需的消息传递基础设施。最后一个元素尤其引起了极大的关注,虽然它利用了其他 ebXML 规范,但也可以单独考虑它,这对于关注消息传递模式很有用。
Several specifications comprise ebXML, covering topics such as how enterprises advertise their business process and search for those of potential partners, how partners agree upon and initiate communication, the use of registries to facilitate the discovery and initialization of business conversations, and the behavior of the messaging infrastructure required to facilitate the conversations. This last element in particular has garnered a great deal of attention, and while it leverages the other ebXML specifications, it can also be considered separately, which is useful for the purposes of focusing on messaging patterns.
ebMS 并不像这里提到的其他一些标准那样“新兴”,因为它作为集成业务流程、增强 EDI 系统和扩展 SOAP 来为 Web 服务提供可靠性和安全性的手段,已经取得了巨大的成功。ebMS 由 OASIS 自 1999 年开始历时三年开发而成,事实证明,它对其他业务流程标准的开发也具有影响力。
ebMS is not quite as "emerging" as some of the other standards mentioned here, as it has already achieved significant success as a means of integrating business processes, augmenting EDI systems, and extending SOAP to provide reliability and security for Web services. Developed by OASIS over a three-year period beginning in 1999, ebMS is also proving influential in the development of other business process standards.
ebMS 的目标是简化 XML 框架内业务消息的交换,但该框架包括使用不一定是 XML 的消息有效负载,有效负载可以采用任何形式,包括传统的 EDI 格式和二进制格式。因此ebMS可以封装现有的消息系统并作为一种灵活的桥接技术,包含一组消息翻译器这对于扩展与外联网的集成以及业务合作伙伴之间的 B2B 通信特别有用。ebMS 用例的一个重要来源是跨不同企业的专有 MOM 系统的链接,并且供应商和开发人员之间已经进行了大量测试来验证这种互操作性。ebMS 的关键在于它支持传统的 EDI 系统,使企业能够利用对 EDI 的长期投资,同时通过结合 Web 和 XML 功能来弥补 EDI 的缺点。
The goal of ebMS is to ease the exchange of business messages within an XML framework, but that framework includes the use of message payloads that are not necessarily XMLthe payload can take any form, including traditional EDI formats as well as binary formats. Thus ebMS can encapsulate existing messaging systems and serve as a flexible bridging technology, containing a set of Message Translator implementations, which is particularly useful in scaling integration to extranets and to B2B communication between business partners. An important source of use cases for ebMS is the linkage of proprietary MOM systems across separate enterprises, and considerable testing between vendors and developers has occurred to verify this interoperability. Critical to ebMS is that it supports legacy EDI systems, allowing enterprises to leverage longstanding investments in EDI while compensating for EDI's shortcomings by incorporating features of the Web and XML.
一方面,ebMS 是 EDI 的兼容迭代,另一方面,它也是基于标准 SOAP 服务的复杂改进。ebMS 系统中使用的 XML 传输由 SOAP 信封组成,其中包含 ebMS 特定的 SOAP 标头,用于记录唯一的消息标识符、时间戳、数字签名以及提供有关消息有效负载的元数据的清单。因此,ebMS 使用 SOAP 标头机制来实现消息路由模式和许多消息传递端点模式,例如幂等接收器和与保证顺序传递相关的其他模式。
While on the one hand ebMS is a compatible iteration of EDI, on the other hand it is also a sophisticated improvement of standard SOAP-based services. The XML transmissions used in an ebMS system consist of SOAP envelopes that include ebMS-specific SOAP Headers to record unique message identifiers, timestamps, digital signatures, and a manifest which provides metadata about the message's payload. Thus, ebMS uses the SOAP Header mechanism to implement the message routing patterns and many of the messaging endpoints patterns, such as Idempotent Receiver and others related to guaranteed sequential delivery.
ebMS 通过使用带有附件的 SOAP 标准来传输其有效负载,将有效负载附加到 SOAP 信封,其方式与将附件添加到电子邮件消息的方式非常相似。同样,这些有效负载没有格式限制:消息的有效负载可以是 XML 数据、二进制数据、对外部数据的基于链接的引用等。此外,每个有效负载可以具有其自己的数字签名,并且附加的认证和授权可以由ebMS实现者提供。
ebMS transports its payloads by using the SOAP with Attachments standard to attach the payload to the SOAP envelope in much the same way that attachments are added to email messages. These payloads, again, have no formatting restrictions placed upon them: A message's payload may be XML data, binary data, link-based references to external data, and so on. Further, each payload may have its own digital signature, and additional authentication and authorization may be provided by ebMS implementers.
ebMS 消息由带有附件的SOAP 消息组成
An ebMS Message Is Composed as SOAP Messages with Attachments
默认情况下异步消息传递,但也可以通过 ebMS 进行同步传递。错误处理机制也相当复杂,并且根据实现可以提供 SOAP 错误信息以及特定于负载的错误消息。
Asynchronous message delivery is the default, but synchronous delivery is also possible through ebMS. The error-handling mechanism is also quite sophisticated, and depending on the implementation can provide SOAP Fault information along with payload-specific error messages.
为了提供可靠性,ebMS 实现的关键元素(称为 ebXML 消息服务处理程序)将消息保留在对话的发送端。开发人员可以为单个消息或消息组提供语义声明,例如“一次且仅一次”和“存储并转发”。ebMS 还指定了管理顺序交付和管理的服务;最后一项服务是通过代表控制总线的消息状态服务来实现的。它提供了请求先前发送到 ebMS 系统的消息状态的能力,该系统在幕后通过消息历史记录和相关模式的实现进行操作。
To provide reliability, the critical element of an ebMS implementationcalled ebXML Message Service Handlerspersist messages at the sending end of a conversation. Developers can provide semantic declarations such as "once-and-only-once" and "store-and-forward" for individual messages or for groups of messages. ebMS specifies services to manage sequential delivery and management as well; this last service is implemented through a Message Status Service that represents a Control Bus. It provides the ability to request the status of a message previously sent into the ebMS system, which underneath the covers operates through an implementation of Message History and related patterns.
有关 ebXML 的更多信息,请访问http://www.ebxml.org/。ebMS 规范可在http://www.ebxml.org/specs/ebMS2.pdf中找到。
More information on ebXML can be found at http://www.ebxml.org/. The ebMS specification can be found at http://www.ebxml.org/specs/ebMS2.pdf.
用于定义业务流程组件及其交互的流行新兴标准是 Web 服务业务流程执行语言 (BPEL4WS)。这个名字通常发音为“bee-pel”,甚至更糟糕,“bee-pel”的意思是“wuss”。该标准代表了来自 IBM 和 Microsoft 的两个竞争提案的合并。IBM 的 Web 服务流语言 (WSFL) 指定了一种创建和链接服务端点以编排 Web 服务工作流的方法,而 Microsoft 的 XLANG 提供了用于创建在 Microsoft BizTalk 服务器产品中实现的工作流组件的语法和开发模型。BPEL4WS 提供了两者的一部分:它包括用于从现有服务组合流程和描述流程接口的语法,以便在更大的工作流中将它们链接在一起。它是由 BEA Systems、IBM 和 Microsoft 之间的临时合作创建的,并已提交给 OASIS。
A popular emerging standard for defining business process components and their interactions is Business Process Execution Language for Web Services (BPEL4WS). The name is often pronounced bee-pelor even worse, bee-pel for wuss. This standard represents the merging of two competing proposals from IBM and from Microsoft. IBM's Web Services Flow Language (WSFL) specified a means of creating and linking service endpoints to choreograph Web service workflows, while Microsoft's XLANG provided a syntax and development model for creating workflow components as realized in the Microsoft BizTalk server product. BPEL4WS provides a bit of both: It includes a syntax for composing processes from existing services and for describing process interfaces for the purpose of linking them together in larger workflows. It was created by an ad hoc collaboration between BEA Systems, IBM, and Microsoft, and has been submitted to OASIS.
BPEL 的设计要求 Web 服务的链接(称为合作伙伴),根据称为活动将 XML 消息放入或取出消息存储(称为容器)。服务链接、消息容器和活动的逻辑集构成单个业务流程组件。BPEL 规范本质上定义了一个流程管理器,用于创建Routing Slip 、 Durable Subscriber 、 Datatype Channel,以及其他基于声明性 XML 语法的内容。开发人员在 XML 中声明行为(也许借助可视化工具),以便制作业务流程组件(例如第 630 页上的图中所示的组件),并且不需要以编程方式创建服务之间的消息传递。
The design of BPEL calls for linkages of Web services, named partners, to put XML messages into and out of message stores, called containers, according to rules called activities. A logical set of service linkages, message containers, and activities comprise a single business process component. The BPEL specification essentially defines a Process Manager that creates Routing Slip, Durable Subscriber, Datatype Channel, and others based on a declarative XML syntax. Developers declare behaviors in XML (perhaps with the aid of visual tools) in order to craft business process components (such as the one shown in the figure on page 630), and do not need to programmatically create the messaging between services.
WSDL 文档对于 BPEL 非常重要,因为 BPEL 组件将包含并管理基于服务的 WSDL 文档的多个 Web 服务。业务流程使用根据 WSDL 消息声明格式化的消息来实例化消息并将其路由到在这些服务的 WSDL 文档中声明的服务端点。BPEL 语法通过导入一组 Web 服务的 WSDL 文件并声明使用什么序列来接收服务消息、将消息发送到何处、何时以及如何回复、何时调用其他 Web 服务,从而链接一组 Web 服务的 portType和操作,以及等等。
WSDL documents are very important to BPEL, as the BPEL component will consist of and manage multiple Web services based on the services' WSDL documents. The business process instantiates and routes messages to the service endpoints declared within these services' WSDL documents using messages that are formatted according to the WSDL message declarations. The BPEL syntax links the portType and operations of a set of Web services by importing their WSDL files and declaring what sequences to use to receive the service messages, where to send them, when and how to reply, when to invoke other Web services, and so forth.
为了建立服务之间的链接,应用程序开发人员导入组成组件的 Web 服务的 WSDL,并使用 BPEL serviceLinkType元素定义它们之间的合作伙伴关系。该元素指的是流程中涉及的 Web 服务的portType以及它们在流程中扮演的合作伙伴角色;然后,开发人员在声明消息应如何流入和流出这些链接和合作伙伴角色时就能够引用这些链接和合作伙伴角色。
In order to establish the linkages between services, the application developer imports the WSDL of the Web services comprising the component and defines the partner relationships between them using the BPEL serviceLinkType element. This element refers to the portTypes of the Web services involved in a flow and the partner roles that they play in relation to the process; the developer is then able to refer to these linkages and partner roles when declaring how messages should flow into and out of them.
声明服务之间的关系后,开发人员使用 BPEL 创建容器来保存服务用作输入和输出的消息。容器非常类似于WSDL 中定义的消息类型的数据类型通道。根据底层容器实现,也可以从共享数据库集成风格的角度考虑容器,但增加了封装和内置协作语义;容器是由单独的服务使用的共享数据存储库。除了通过 BPEL 元素将容器连接到服务之外,还可以通过 XPath 扩展直接访问容器的内容。
After declaring the relationships between services, the developer uses BPEL to create containers to hold the messages that the services use as input and output. Containers are very much like a Datatype Channel for the message types defined in WSDL. Depending on the underlying container implementation, a container could also be considered from the perspective of the Shared Database integration style, but with added encapsulation and built-in collaboration semantics; a container is a shared data repository used by separate services. In addition to connecting containers to services through the BPEL elements, a container's contents can be accessed directly via XPath extensions.
BPEL 组件本身将其自己的输入和输出点公开为单个接口,并且其这样做的形式与其内部 Web 服务使用的形式相同:通过 WSDL。然而,很明显,业务流程组件的 WSDL 与 Web 服务 WSDL 文档略有不同,因为业务流程 WSDL 的 portType定义进入和离开该单个流程的入口点,而不是实现的单独逻辑块。作为服务接口中的方法。
A BPEL component itself exposes its own input and output points as a single interface, and it does so in the same form that its internal Web services use: through WSDL. It should be evident, however, that the WSDL of a business process component is slightly different from a Web service WSDL document in that the business process WSDL's portTypes define entry points into and out of that single process, and not separate pieces of logic implemented as methods in a service interface.
BPEL 组件的行为被声明为一组操作。在任何一个特定操作中,业务流程可以向服务发送消息(在这种情况下,Web 服务称为调用伙伴) 、从服务接收消息(在这种情况下,服务称为客户端伙伴)、回复对于客户端合作伙伴发送的消息,根据某种逻辑规则确定是否应该发送或接收消息、等待预定的时间、报告错误、将消息从一个地方复制到另一个地方,或者什么都不做。
The behavior of a BPEL component is declared as a set of actions. In any one specific action, a business processes can send messages to a service (in this case, the Web service is called an invoked partner), receive messages from a service (in this case, the service is called a client partner), reply to a message sent by a client partner, determine whether it should send or receive messages based on some logical rule, wait for a scheduled period of time, report an error, copy a message from one place to another, or do nothing at all.
BPEL XML 语法反映了这些基本操作中的每一个。开发人员使用调用元素将消息发送到被调用的合作伙伴,并使用接收和回复来接收和回复客户端合作伙伴。诸如流和分叉逻辑之类的元素进入并行和基于事件的执行通道。throw元素有助于错误报告,而wait、empty和Terminate元素则暂停或停止执行。构造活动的元素包括while、sequence和pick,并且 BPEL 中的条件是使用 XPath 语句声明的。
The BPEL XML grammar reflects each of these basic actions. A developer uses the invoke element to send messages to an invoked partner and uses receive and reply to receive and reply to a client partner. Elements such as flow and pick fork logic into parallel and event-based channels of execution. The throw element facilitates error reporting, while the wait, empty, and terminate elements pause or halt execution. Elements to structure activities include while, sequence, and pick, and conditionals in BPEL are declared using XPath statements.
一旦开发人员创建了定义将组成业务流程的服务的 XML 源文件,声明了服务将使用的消息容器,并声明了消息交换中涉及的操作序列,他或她就准备好部署该业务流程了。业务流程组件。这就是 BPEL 实现的运行时方面发挥作用的地方。
Once a developer has created the XML source file that defines the services that will compose a business process, declares the message containers that the services will use, and declares the sequence of operations involved in the message exchange, he or she is ready to deploy the business process component. This is where the runtime aspects of BPEL implementations come into play.
除了充当服务关系和消息流的声明之外,BPEL 描述也是可以输入 BPEL4WS 引擎的可执行文件。当发生这种情况时,引擎解释该文件并为应用程序开发人员设置一系列消息传递结构,以连接作为业务流程一部分的 Web 服务。作为流程管理器实现,BPEL4WS 运行时是管理和关联服务之间的消息流的管理实体。BPEL4WS 引擎接受所有必要的文档并动态生成和管理消息传递基础设施。
In addition to acting as declarations of service relationships and message flow, BPEL descriptions are also executable files that can be fed into a BPEL4WS engine. When this happens, the engine interprets the file and sets up a series of messaging constructs for the application developer that connects the Web services that are part of the business process. As a Process Manager implementation, the BPEL4WS runtime is the governing entity that manages and correlates the flow of messages between services. The BPEL4WS engine accepts all of the necessary documents and dynamically generates and manages the messaging infrastructure.
BPEL 规范可以在http://www-106.ibm.com/developerworks/webservices/library/ws-bpel/找到。
The BPEL specification can be found at http://www-106.ibm.com/developerworks/webservices/library/ws-bpel/.
Web 服务编排接口(WSCI;通常读作“威士忌”)解决了 BPEL 解决的相同问题领域。这两个规范最初是由竞争供应商联盟支持的(BEA 除外,该公司似乎通过为这两个规范做出贡献来对冲赌注)。WSCI 得到了 Sun、Intalio、SAP 和 BEA 的支持,并已提交给 W3C。然而,WSCI 的一些最初支持者现在正在向 BPEL 提供支持。
The Web Service Choreography Interface (WSCI; often pronounced whiskey) addresses the same problem domain tackled by BPEL. The two were competing specifications initially backed by competing vendor alliances (with the exception of BEA, which appears to be hedging bets by contributing to both specifications). WSCI was backed by Sun, Intalio, SAP, and BEA, and has been submitted to the W3C. However, several original supporters of WSCI are now lending support to BPEL.
WSCI 受到业务流程建模语言 (BPML) 的影响,虽然与集成模式没有直接关系,但考虑到 WSCI 的历史背景,确实值得一提。BPML 用流程建模中使用的基于 XML 的元语言表示业务流程。BPML 的工作流程方面很大程度上是 WSCI 的一部分,但 BPML 还包括配套的图形符号和查询语言。
WSCI is influenced by Business Process Modeling Language (BPML), which while not directly relevant to integration patterns, does bear mentioning for the sake of WSCI's historical context. BPML represents business processes in an XML-based metalanguage used in process modeling. BPML's workflow aspects are largely part of WSCI, but BPML also includes a companion graphical notation and query language.
与 BPEL 一样,WSCI 认识到企业集成涉及组合服务之间的长时间对话,而不是基本 Web 服务协议所假定的单一操作调用。WSCI 提供了一种将来自多个操作的服务消息链接到这些复合流程中的方法,确保消息按照正确的顺序发送或接收,根据声明性业务规则发送和接收,在需要时以事务方式发送,并且可描绘和描述可作为单个全局流程进行管理。利用 Web 服务相对于传统专有 MOM 解决方案的优势,WSCI 还适应服务的动态发现、异构协议和工作流的分散协调。
Like BPEL, WSCI recognizes that enterprise integration involves lengthy conversations among composite services rather than the single operational invocations assumed by basic Web services protocols. WSCI provides a way to link service messages from multiple operations into these composite processes, ensuring along the way that messages are sent or received in the proper sequences, sent and received according to declarative business rules, sent in transactional fashion when needed, and portrayable and manageable as a single global process. Leveraging Web service advantages over traditional proprietary MOM solutions, WSCI also accommodates the dynamic discovery of services, heterogeneous protocols, and decentralized coordination of workflow.
与 BPEL 一样,WSCI 严重依赖 WSDL 的服务端点、portTypes 、操作和消息类型的通告。WSCI 操作直接映射到编排中服务的 WSDL 中公开的操作。此外,WSCI 语法直接嵌入到 WSDL 文件中;这些声明要么位于 WSDL 文档中(该文档导入编排服务的 WSDL 文档),要么位于正在编排操作的单个服务的 WSDL 文件中。WSCI 元素包含在 WSDL定义元素中。
Like BPEL, WSCI relies heavily upon WSDL's advertisement of service endpoints, portTypes, operations, and message types. WSCI actions directly map to operations exposed in the WSDL of the services within a choreography. Moreover, the WSCI syntax is embedded directly within a WSDL file; the declarations sit either in a WSDL document, which imports the WSDL documents of the choreographed services, or within the WSDL file of a single service whose operations are being choreographed. WSCI elements are contained within the WSDL definitions element.
WSCI 的基本构造是 Web 服务消息传递发生的操作。操作在流程元素内分组,以便声明它们按顺序、并行、循环或有条件地发生。一组进程通过接口声明进行分组;该接口元素是流程组件的接口,它直接嵌入到 Web 服务的 WSDL 定义中。许多活动元素与 BPEL 的活动元素类似,并且像它们的 BPEL 对应项一样,它们包括对 XPath 表达式的支持。
The fundamental construct of WSCI is the action in which Web service messaging occurs. Actions are grouped within process elements so that they are declared to occur sequentially, in parallel, in loop, or conditionally. A set of processes is grouped with an interface declaration; this interface element is the interface to the process component, and it is directly embedded within the WSDL definitions of a Web service. Many of the activity elements are similar to those of BPEL, and like their BPEL counterparts, they include support for XPath expressions.
WSCI 规范可在http://wwws.sun.com/software/xml/developers/wsci/wsci-spec-10.pdf中找到。
The WSCI specification can be found at http://wwws.sun.com/software/xml/developers/wsci/wsci-spec-10.pdf.
JCP 创建受非 Java 标准影响的 Java 语言 API 和绑定。特别是,对象管理组织 (OMG) 和 W3C 制定的标准已受到 JCP 的影响。最近,JCP 已将这种方法转向由 WS-I 等团体开发的 Web 服务标准,并且目前正在开发两个新的、大肆宣传的 JSR,以解决业务流程组件的 Java 绑定问题: Java 流程定义 (JSR-207) ),由 BEA Systems 提交,以及 Java Business Integration (JSR-208),由 Sun Microsystems 提交。虽然乍一看这两个提议似乎有重叠,但实际上它们是相当互补的。JSR-207 为开发人员指定了一种使用附加到 Java 代码的元数据快速轻松地制作消息传递或处理组件的方法;JSR-208 指定这些组件如何相互交互、与容器交互以及与 J2EE 和 Web 服务世界的其余部分交互。因此,JSR-207 或多或少是一个微观视图,而 JSR-208 是如何在 Java 中标准化流程组件之间的消息传递的宏观视图。
The JCP creates Java language APIs and bindings influenced by non-Java standards. In particular, standards developed by the Object Management Group (OMG) and the W3C have been shadowed by the JCP. Recently, the JCP has turned this approach toward Web services standards developed by groups such as the WS-I, and two new, much-ballyhooed JSRs are now underway to address Java bindings for business process components: Process Definition for Java (JSR-207), submitted by BEA Systems, and Java Business Integration (JSR-208), submitted by Sun Microsystems. While at first glance these two proposals may seem to overlap, they are actually fairly complementary. JSR-207 specifies a way for developers to craft messaging or process components quickly and easily using metadata attached to Java code; JSR-208 specifies how those components will interact with each other, with containers, and with the rest of the J2EE and Web services world. So, JSR-207 is more or less a micro-view, and JSR-208 is a macro-view of how the messaging between process components is to be standardized in Java.
Process Definition for Java (JSR-207) 由 BEA Systems 提交,旨在定义用于在 Java/J2EE 环境中创建业务流程的元数据、接口和运行时模型。这个非常重要的 JSR 旨在指定使用 Java 语言和类似 Javadoc 的元数据注释来构建业务流程组件的标准方法。作为 J2EE 的补充,该机制还可用于构建业务流程计划的 Java 实现,例如 BPEL4WS、WSCI 以及 W3C Choreography 工作组生成的计划。
Process Definition for Java (JSR-207), submitted by BEA Systems, aims to define metadata, interfaces, and a runtime model for creating business processes in the Java/J2EE environment. This very important JSR intends to specify the standard means of crafting business process components using the Java language and Javadoc-like metadata annotations. Proposed as an addition to J2EE, the mechanism could also be used to build Java implementations of business process initiatives such as BPEL4WS, WSCI, and those produced by the W3C Choreography Working Group.
该技术建立在 Java 语言元数据技术 (JSR-175) 之上,以便提供用于描述业务流程的简单语法。元数据可以直接应用于Java源代码,以动态生成和绑定流程行为,包括对异步消息传递、并行执行、消息关联、消息路由、错误处理和其他常见流程活动的支持。因此,元数据语义需要足够丰富,以支持组件容器所需的参数,以便在组件部署时动态设置消息传递基础结构并处理第 5 章“消息构造”中介绍中描述的问题。
This technology builds upon Java Language Metadata technology (JSR-175) in order to supply a simple syntax for describing business processes. Metadata can be applied directly to Java source code to dynamically generate and bind process behaviors, including support for asynchronous messaging, parallel execution, message correlation, message routing, error handling, and other common flow activities. The metadata semantics therefore need to be rich enough to support the parameters needed for a component's container to dynamically set up the messaging infrastructure and handle issues described in the introduction in Chapter 5, "Message Construction," upon the component's deployment.
值得注意的是,开发人员在 J2EE 中构建业务流程组件不需要此 JSR。如今构建这样的流程是可能的,但这是一项费力的工作,需要开发人员在非常低的级别应用消息传递模式,并导致工作流程的维护成本高昂。该规范旨在简化流程组件的创建,以便开发人员可以在更高的水平上应用他们的技能,更快地创建更强大的应用程序,并且随着时间的推移,开发和管理的成本更低。
It is worth noting that this JSR is not needed to enable developers to build business process components in J2EE. It is possible to build such processes todaybut it is laborious work that requires developers to apply messaging patterns at a very low level and results in workflows that are costly to maintain. This specification intends to simplify the creation of process components so that developers can apply their skills at a higher level, creating more powerful applications more rapidly that are less expensive to evolve and administer over time.
有关 Java 流程定义的更多详细信息,请访问http://www.jcp.org/en/jsr/detail?id=207。
More details on Process definition for Java can be found at http://www.jcp.org/en/jsr/detail?id=207.
Sun 在 JSR-208 中提出了 Java 业务集成 (JBI),旨在定义服务提供者接口 (SPI),以便为 WSCI、BPEL4WS 等规范以及 W3C Choreography 工作组制定的工作创建业务集成环境。JBI 没有提出新的 Java API 或注释,但包含新的部署和打包机制。JBI 没有为开发人员添加 API,而是专注于集成基础设施,其 SPI 主要对创建消息传递和流程组件模型以在 Java 设置中执行集成工作的产品供应商可见。JBI 专家组的目标是遵循 W3C 编排工作组的领导,并确保该组的工作能够无缝地融入 J2EE 平台。
Sun proposed Java Business Integration (JBI) in JSR-208 with the intention of defining service provider interfaces (SPIs) for creating a business integration environment for specifications such as WSCI, BPEL4WS, and the work produced by the W3C Choreography Working Group. JBI proposes no new Java APIs or annotations, but does include a new deployment and packaging mechanism. Instead of adding APIs for developers, JBI focuses on integration infrastructure, and its SPIs will be visible primarily to product vendors who create messaging and process component models for performing integration work in a Java setting. The JBI expert group aims to follow the lead of the W3C Choreography Working Group and ensure that the work of that group will fit seamlessly into the J2EE platform.
JBI 有一个相当崇高的目标:映射各种系统和协议标准,即用于描述进程之间关系的多种语法,包括专有的和特定于供应商的语法以及成为彼此标准和 J2EE 标准的语法。它为消息编排提供 Java 绑定,而不管底层消息和流程细节如何。它还包括一个新的打包机制,该机制扩展了 J2EE 打包(例如 WAR、JAR、RAR 和 EAR)以支持将 JBI 组件部署到 J2EE 环境中。
JBI has a fairly lofty goal: Map various systems and protocol standardsthat is, multiple syntaxes used to describe relationships between processes, including syntaxes that are proprietary and vendor-specific as well as those that become standardto one another and to J2EE. It provides a Java binding for message choreography regardless of the underlying message and process specifics. It also includes a new packaging mechanism that extends J2EE packaging (such as WAR, JAR, RAR, and EAR) to support the deployment of a JBI component into a J2EE environment.
JBI 认为支持流程组件所需的三个主要角色:绑定、机器和环境。绑定与通信格式有关,包括消息格式和网络传输及其映射,并围绕工作流格式(例如 BPEL)形成一个保护伞;机器是托管和管理业务流程的服务和流程容器;该环境是一个总体流程管理系统,它将异构机器和绑定相互链接。JBI 关注环境作为集成系统的核心,并指定机器和绑定如何与其交互。JBI 打包和部署机制旨在提供一种以标准方式将进程与环境挂钩的方法,
JBI sees three principal roles required for supporting process components: bindings, machines, and the environment. Bindings are about communication formats and include message formats and network transports along with their mappings as well as form an umbrella around workflow formats such as BPEL; machines are the service and process containers that host and manage business processes; the environment is the overarching process management system that links heterogeneous machines and bindings to one another. JBI focuses on the environment as the core of the integration system and specifies how machines and bindings interact with it. The JBI packaging and deployment mechanism is intended to provide a means of hooking processes up to the environment in a standard way, but the actual creation of those components is outside the scope of the specification.
JBI 将上述 Java 流程定义 (JSR-207) 视为组合流程组件的一种方式,并且托管这些组件的运行时适合 JBI 机器的类别。该机器应该实现Message Translator 、 Service Activator 、 Envelope Wrapper 以及通过 JBI 公开其格式和协议行为所需的其他模式,以确保它可以与集成环境正确集成。
JBI views the Process Definition for Java (JSR-207) described above as a way of composing process components, and the runtime that will host those components fits into the category of a JBI machine. That machine should implement the Message Translator, Service Activator, Envelope Wrapper and other patterns necessary to expose their formats and protocol behaviors through JBI to ensure that it can integrate correctly with the integration environment.
有关 JBI 的更多详细信息,请访问http://www.jcp.org/en/jsr/detail?id=208。
More details on JBI can be found at http://www.jcp.org/en/jsr/detail?id=208.
与刚刚描述的流程集成规范相比,许多 Web 服务规范的雄心勃勃,但正在出现许多 Web 服务规范来扩展基于 SOAP 和 WSDL 的 Web 服务,以包括可靠性、安全性、状态性和服务质量。这些规范通常可通过WS-前缀进行识别,它们建立在 W3C 技术之上,并且每个规范都解决一个相当具体的问题。
Less ambitious than the process integration specifications just described, a number of Web services specifications are emerging to extend SOAP- and WSDL-based Web services to include reliability, security, statefulness, and quality of service. Typically identifiable by the WS- prefix, these specifications build on the W3C technologies, and each addresses a fairly specific problem.
不幸的是,Web 服务标准格局已经变得混乱不堪,充斥着由相互竞争的供应商联盟支持的相互竞争的版本,并且也许正是这种竞争的结果,许多这些标准如今很少被广泛实施。标准的调整似乎即将来临。尽管如此,这些规范还是值得注意的,因为它们的习惯用法和策略可能对将 SOAP 和 WSDL 等 Web 服务技术应用于基于消息传递的企业集成的应用程序开发人员有用。这些标准也肯定会在提出这些标准的供应商的产品中找到一席之地,包括 IBM、BEA、Microsoft 和 Oracle 的产品。
Unfortunately, the Web services standards landscape has become muddled with competing versions backed by competing vendor alliances, and perhaps as a result of this competition, many of these standards are rarely seen implemented in the wild today. A standards shake-out seems to be looming. Nevertheless, these specifications are worth noting, as their idioms and tactics might prove useful to application developers who are applying Web services technologies like SOAP and WSDL to messaging-based enterprise integration. These standards are also certain to find a home in the products of the vendors who propose them, including those of IBM, BEA, Microsoft, and Oracle.
此类别中一些更值得注意的规范包括处理可事务性、可靠性、路由、会话状态和安全性的规范。这些内容将在以下几页中进行描述。
A few of the more notable specifications in this category include those dealing with transactability, reliability, routing, conversational state, and security. These are described in the following pages.
由于无状态且不可靠,最流行的 Web 服务协议和传输无法提供事务处理所需的服务质量。这个缺点发展成为一个极其重要的问题。实际上,这意味着如果开发人员希望使用基于 Web 服务的集成机制,则该开发人员必须在这些集成机制中发展他或她自己的事务方案。通过异步消息传递发生的服务调用的集合必须能够以原子方式进行批处理,以便消息作为一个可以同时失败或回滚的单元,或者作为一个失败可以触发某种形式的补偿的单元。这在专有 MOM 系统中很常见。
Being stateless and unreliable, the most popular Web services protocols and transports do not provide the quality of service required by transactional processes. This shortcoming blossoms into a critically important problem. In practice, it means that if a developer wishes to use a Web servicesbased integration mechanism, that developer must grow his or her own transaction scheme within those integration mechanisms. A collection of service invocations that occur through asynchronous messaging must be capable of being batched atomically so that the messages function as a unit that can fail or roll back all at once or as a unit whose failure can trigger some form of compensation. This is common in proprietary MOM systems. The Web Services Coordination and Web Services Transaction specifications tackle this problem for Web services.
WS-Coordination 由 BEA、Microsoft 和 IBM 起草,指定了一种由参与流的所有服务创建和传播上下文信息的方法,甚至可以异步地并在锯齿状时间间隔内传播。该规范描述了一个可扩展框架,用于创建协调应用程序和服务的操作的协议。这些协调协议通过创建和注册基于 XML 的上下文来发挥作用,这些上下文通过 SOAP 消息传播并由位于交互中所有端点的协调器使用。
WS-Coordination, drafted by BEA, Microsoft, and IBM, specifies a way to create and propagate contextual information by all of the services participating in a flow, even asynchronously and over jagged time intervals. The specification describes an extensible framework for creating protocols that coordinate the actions of applications and services. These coordination protocols function by creating and registering XML-based contexts that are propagated with SOAP messages and used by coordinators located at all endpoints in an interaction.
此类上下文可用于支持许多应用程序行为,例如需要就分布式事务的结果达成一致的行为。因此,WS-Transaction 规范使用 WS-Coordination 来实现跨服务调用的分布式事务性。
Such contexts can be used to support a number of application behaviors, such as those that need to reach consistent agreement on the outcome of distributed transactions. Accordingly, the WS-Transaction specification uses WS-Coordination to implement distributed transactability across service invocations.
WS-Transaction 定义了一种监视和衡量流程中每个操作成功或失败的方法。实际上,这意味着当 SOAP 消息到达端点时,必须过滤并从消息中提取包含协调上下文的 SOAP 标头(实现可以使用内容过滤器和拆分器模式来执行此任务),然后将其发送到事务协调器以便解释。
WS-Transaction defines a way to monitor and measure the success or failure of each action in a flow. Practically, this means that when a SOAP message arrives at an endpoint, the SOAP Header containing the coordination context must be filtered and pulled from the message (implementations may employ the Content Filter and Splitter patterns for this task) and then sent to the transaction coordinator for interpretation.
下面的 SOAP 信封摘录提供了一个协调上下文的简单示例,该协调上下文用于使 SOAP 操作可通过 WS-Transaction 进行事务处理。协调器使用 SOAP 标头中的上下文信息来注册应用程序以接收事务事件,例如登记、两阶段提交过程的准备阶段、回滚和提交。
The SOAP envelope excerpt below provides a simple example of a coordination context used for making SOAP operations transactable through WS-Transaction. This context information in the SOAP header is used by coordinators to register applications to receive transaction events such as enlistments, the prepare stage of a two-phase commit process, rollbacks, and commits.
<SOAP-ENV:信封 xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
<SOAP-ENV:标头>
<wscoor:CoordinationContext
xmlns:wscoor="http://schemas.xmlsoap.org/ws/2002/08/wscoor"
xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
xmlns:myTransactableApp="http://foo.com/baz">
<wsu:标识符>http://foo.com/baz/bar</wsu:标识符>
<wsu:过期>2004-12-31T18:00:00-08:00</wsu:过期>
<wscoor:协调类型>
http://schemas.xmlsoap.org/ws/2002/08/wstx
</wscoor:协调类型>
<wscoor:注册服务>
<wsu:地址>
http://foo.com/coordinationservice/registration
</wsu:地址>
</wscoor:注册服务>
<myTransactableApp:隔离级别>
可重复读取
</myTransactableApp:隔离级别>
</wscoor:CoordinationContext>
</SOAP-ENV:标头>
<!-- 肥皂体(剪断)-->
</SOAP-ENV:信封>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2001/12/soap-envelope">
<SOAP-ENV:Header>
<wscoor:CoordinationContext
xmlns:wscoor="http://schemas.xmlsoap.org/ws/2002/08/wscoor"
xmlns:wsu="http://schemas.xmlsoap.org/ws/2002/07/utility"
xmlns:myTransactableApp="http://foo.com/baz">
<wsu:Identifier>http://foo.com/baz/bar</wsu:Identifier>
<wsu:Expires>2004-12-31T18:00:00-08:00</wsu:Expires>
<wscoor:CoordinationType>
http://schemas.xmlsoap.org/ws/2002/08/wstx
</wscoor:CoordinationType>
<wscoor:RegistrationService>
<wsu:Address>
http://foo.com/coordinationservice/registration
</wsu:Address>
</wscoor:RegistrationService>
<myTransactableApp:IsolationLevel>
RepeatableRead
</myTransactableApp:IsolationLevel>
</wscoor:CoordinationContext>
</SOAP-ENV:Header>
<!-- SOAP BODY (snipped) -->
</SOAP-ENV:Envelope>
WS-Transaction 规范定义了两种协调类型供开发人员在其应用程序中使用:原子事务 (AT) 和业务活动 (BA)。
The WS-Transaction specification defines two coordination types for developers to use in their applications: atomic transaction (AT) and business activity (BA).
原子事务很好地映射到传统的分布式事务技术,例如 XA。它们对于相对短暂的操作很有用,在这些操作中,诸如线程和部分数据源之类的资源锁定是可以接受的,并且绝对回滚是有意义的。WS-Transaction 提供了一种将专有 XA 实现(包括对两阶段提交的支持)链接到 Web 服务的方法。
Atomic transactions map fairly well to traditional distributed transaction technology, such as XA. They are useful for relatively short-lived operations in which locking of resourcessuch as threads and portions of data sourcesare acceptable and in which absolute rollbacks make sense. WS-Transaction provides a means of linking proprietary XA implementations, including support for two-phase commit, to Web services.
业务活动通常是长期进程,可能由许多原子事务组成。如果业务活动中出现单一故障情况,则通常不希望进行全局回滚;相反,业务活动中一个原子事务的失败通常应该触发另一组服务调用和消息交换。这些交换可能包括补偿技术,以保留部分业务活动历史的方式从错误中恢复。一个示例是业务活动,包括用户在两三天内预订航班、租车、预订酒店房间以及预订剧院门票。该流程中的每个活动都可能是一个原子事务,
A business activity is typically a long-lived process that may consist of a number of atomic transactions. A global rollback is usually not desirable in the event of a single failure condition in a business activity; instead, a failure of one atomic transaction within a business activity should often trigger another set of service invocations and message exchanges. These exchanges might include compensation techniques to recover from the error in a way that preserves part of the history of the business activity. An example is a business activity that includes the booking of an airline flight, rental of a car, reservation of a hotel room, and reservation of theater tickets that a user makes over a two- or three-day period. Each activity in this flow may be an atomic transaction, and if one of the events should fail, the overarching business activity of making the travel arrangements should adjust via compensation rather than through coordinated rollback of all atomic transactions.
WS-Coordination 和 WS-Transaction 规范有望在服务消息传递期间发生故障时提供可靠、丰富的行为。当他们开始进入产品时,他们应该减轻开发人员的负担,围绕基于消息的服务应用程序打造自己的可事务性和上下文服务。
The WS-Coordination and WS-Transaction specifications are promising attempts to provide reliable, rich behaviors in the event of failures during service messaging. As they begin to wind their way into products, they should take the burden off of developers to craft their own transactability and contextual services around message-based service applications.
有关 WS-Transaction 的信息可以在http://dev2dev.bea.com/technologies/webservices/ws-transaction.jsp找到。
Information on WS-Transaction can be found at http://dev2dev.bea.com/technologies/webservices/ws-transaction.jsp.
Web 服务之间基于消息的交互通常需要可靠、可保证的消息传递,即使在网络、应用程序或组件发生故障时也是如此,并且需要包含持久性机制和重新发送语义。然而,最流行的 Web 服务技术并不提供这种可靠性。例如,没有增强功能的 SOAP 在许多企业消息传递场景中并不全面有用,因为它最流行的绑定不能可靠地保证消息传递。
It is common for message-based interactions between Web services to require reliable, guaranteeable messaging even in the event of network, application, or component failures and to include persistence mechanisms and resend semantics. The most popular Web services technologies do not provide such reliability, however; SOAP without enhancements, for example, isn't comprehensively useful in many enterprise messaging scenarios because its most popular bindings don't reliably guarantee message delivery.
为了解决这个问题,应用程序开发人员通常被迫使用 Web 服务机制(例如 SOAP 标头)的可扩展性来实现可靠性。为了消除应用程序开发人员承担此任务的需要,正在出现新的标准来解决 Web 服务的可靠性问题。两个这样的规范是 Web 服务可靠性 (WS-Reliability) 和 Web 服务可靠消息传递 (WS-ReliableMessaging)。正如新兴的 Web 服务标准领域中常见的那样,这两个标准是相互竞争的,并由针对同一问题领域的不同供应商组支持。
To remedy this, application developers are typically forced to implement reliability themselves using the extensibility of Web services mechanisms such as SOAP Headers. To eliminate the need for application developers to take on this task, new standards are emerging to tackle Web services reliability. Two such specifications are Web Services Reliability (WS-Reliability) and Web Services Reliable Messaging (WS-ReliableMessaging). As is common in the nascent Web services standards space, these two standards are competing, backed by different groups of vendors aimed at the same problem domain.
WS-Reliability 为基于 SOAP 的 Web 服务提供了异步交换消息的能力,并且保证交付、无重复且消息排序。它是用于管理消息聚合和排序的 SOAP 标准,并提供用于实现保证交付和重新排序器模式等的标准策略。WS-Reliability 利用 SOAP 标头机制来添加标头元素MessageHeader 、 ReliableMessage 、 MessageOrder和RMResponse到 SOAP 消息。这些元素表示消息标识符,例如组 ID 和序列号、时间戳、生存时间值、消息类型值、发送者和接收者信息以及确认回调信息。WS-Reliability由Sun、Oracle、Sonic等多家厂商开发,并已提交给OASIS。它很大程度上受到 ebMS 功能的影响。
WS-Reliability provides SOAP-based Web services with the ability to exchange messages asynchronously with guaranteed delivery, without duplicates, and with message ordering. It is a SOAP standard for managing message aggregation and sequencing, and provides a standard tactic for implementing the Guaranteed Delivery and Resequencer patterns, among others. WS-Reliability leverages the SOAP Header mechanism to add the header elements MessageHeader, ReliableMessage, MessageOrder, and RMResponse to SOAP messages. These elements denote message identifiers such as group IDs and sequence numbers, timestamps, time-to-live values, message type values, sender and receiver information, and acknowledgment callback information. WS-Reliability is produced by a number of vendors, including Sun, Oracle, and Sonic, and it has been submitted to OASIS. It is heavily influenced by the functionality of the ebMS.
为了符合 WS-Reliability,SOAP 消息的接收方必须使用SOAP 标头中的<RMResponse>元素来响应错误或确认。如果没有收到这样的确认,则发送方使用相同的消息标识符重新发送相同的消息。发送方需要保留消息,直到其生存时间值到期或发生确认或失败;接收方还需要将消息持久化,直到它能够可靠地传输到应用层。
To conform to WS-Reliability, the receiver of a SOAP message must respond with either a fault or an acknowledgment using the <RMResponse> element in a SOAP header. If such an acknowledgment is not received, then the sender resends the same message using the same message identifier. The sender is required to persist the message until its time-to-live value has expired or until acknowledgment or failure has occurred; the receiver is also required to persist the message until it can be reliably transmitted to the application layer.
为了确保强制执行一次且仅一次的消息行为,WS-Reliability 提供了一种可以根据应用程序需求启用的序列号机制。组合在一起的单独消息可以共享相同的组标识符,但会在 SOAP 标头中通告它们自己的序列号,从而允许接收者在将消息传递到应用程序之前对消息进行重新排序。
To ensure that once-and-only-once message behaviors are enforced, WS-Reliability provides a sequence number mechanism that can be enabled based on application requirements. Separate messages that are grouped together may share the same group identifier but will advertise their own sequence numbers within the SOAP header, allowing receivers to resequence the messages before delivering them to the application.
WS-Reliability 规范可以在http://www.oasis-open.org/committees/documents.php?wg_abbrev=wsrm中找到。
The WS-Reliability specification can be found at http://www.oasis-open.org/committees/documents.php?wg_abbrev=wsrm.
WS-ReliableMessaging 规范描述了一个类似的协议,该协议允许在出现故障时在分布式应用程序之间可靠地传递消息。该协议以独立的方式描述,允许使用各种网络传输技术和绑定来实现。该规范确实包含一种针对 SOAP 的特定绑定。WS-ReliableMessaging 得到了 BEA、IBM 和 Microsoft 的支持,但尚未发布给标准机构。
The WS-ReliableMessaging specification describes a similar protocol that allows messages to be delivered reliably between distributed applications in the presence of failures. The protocol is described in an independent manner, allowing it to be implemented using a variety of network transport technologies and bindings. The specification does include one specific binding for SOAP. WS-ReliableMessaging is backed by BEA, IBM, and Microsoft, and has not yet been released to a standards body.
WS-ReliableMessaging 的运行原理与 WS-Reliability 相同。它对确认、回调和标识符进行了类似的使用;意味着持久消息缓存的类似用法;并提供详细的故障信息。它确保消息按照四种基本传送保证中的任意一种进行传送:最多一次、至少一次、恰好一次和按顺序。下面说明了通过 WS-ReliableMessaging 传递的消息序列的示例。
WS-ReliableMessaging operates on the same principles as WS-Reliability. It makes similar use of acknowledgments, callbacks, and identifiers; implies similar usage of persistent message caches; and offers detailed fault messages. It ensures that messages are delivered according to any of four basic delivery assurances: At most once, at least once, exactly once, and in order. An example of a message sequence delivered through WS-ReliableMessaging is illustrated below.
WS-ReliableMessaging 通过一系列依赖于插入 SOAP 标头元素中的唯一 ID 的确认回调来提供有保证的顺序传送
WS-ReliableMessaging Provides Guaranteed Sequential Delivery through a Series of Acknowledgment Callbacks That Rely on Unique IDs Inserted into the SOAP Header Element
这两个相互竞争的可靠性规范之间的一个主要区别是 WS-ReliableMessaging 包括使用其他关键 Web 服务规范,例如 WS-Security 和 WS-Addressing。实际上,这意味着 WS-ReliableMessaging 使用其他标准中的特定惯用语来提供此信息,而 WS-Reliability 支持这些功能,但尚未坚持其他新标准中指定的惯用形式。
A key difference between the two competing reliability specifications is that WS-ReliableMessaging includes usage of other critical Web services specifications, such as WS-Security and WS-Addressing. In practice, this means that WS-ReliableMessaging uses specific idioms from other standards for supplying this information, whereas WS-Reliability supports the features but does not yet insist on the idiomatic forms specified in the other new standards.
有关 WS-ReliableMessaging 的更多信息,请访问http://dev2dev.bea.com/technologies/webservices/ws-reliablemessaging.jsp。
More information on WS-ReliableMessaging can be found at http://dev2dev.bea.com/technologies/webservices/ws-reliablemessaging.jsp.
Web 服务会话 (WS-Conversation) 指定用于管理发送方和接收方之间(通常跨两个 SOAP 端点)的有状态异步消息交换的协议。与依靠封装业务流程组件来管理多个合作伙伴之间的有状态消息交换相比,该提议提供了一种在单个客户端和单个服务(其中客户端也可能是服务)之间完成相同事情的简单方法。
Web Services Conversation (WS-Conversation) specifies a protocol for managing stateful asynchronous message exchange between a sender and receiver, usually across two SOAP endpoints. In contrast to relying on an encapsulating business process component for managing stateful message exchange among multiple partners, this proposal provides a simple means of accomplishing the same thing between a single client and single service (where the client may also be a service).
该协议利用 SOAP 标头机制将标识符或令牌 ID 与 SOAP 消息一起发送。当有状态对话开始时,StartHeader元素用于提供对话标识符和回调 URI。作为同一对话一部分的后续消息使用ContinueHeader和CallbackHeader 来进行进一步的请求和回复,并且这些元素将包含在对话开始时建立的相同标识符。
The protocol leverages the SOAP Header mechanism to send identifiers, or token IDs, along with SOAP messages. When a stateful conversation is begun, the StartHeader element is used to supply a conversation identifier and callback URI. Subsequent messages that are part of the same conversation use the ContinueHeader and the CallbackHeader for further requests and replies, and these elements will include the same identifier established when the conversation was started.
SOAP 标头机制可用于实现这些模式,而无需使用这些标准机制,尽管为跨平台的客户端和服务建立通用机制有好处。它是聚合器和组合消息处理器模式的形式化,使用相关标识符来将多个消息映射到单个会话。
The SOAP Header mechanism can be used to implement these patterns without use of these standard mechanisms, though there is benefit in establishing a common mechanism for clients and services across platforms. It is a formalization of the Aggregator and Composed Message Processor patterns using Correlation Identifier for the purposes of mapping multiple messages to a single session.
通过流程组件或编排来外部化状态管理的方法对于更大规模的集成项目来说似乎更有前景,为了简单起见,将服务和组件视为黑匣子是有用的,但是 WS-Conversation 是一个可以在以下情况下完成的示例:可以更好地控制服务和客户。
The approach of externalizing state management through a process component or choreography seems more promising for larger scale integration projects where it is useful for simplicity's sake to consider the services and components as black boxes, but WS-Conversation is an example of what can be done when greater control over the services and clients is possible.
虽然 WS-ReliableMessaging、WS-Coordination 和 WS-Addressing 等标准提供了各种识别消息发送者的机制,但这些规范都不能保证发送者实际上拥有其声称的身份。WS-I Basic ProfileXML Schema、SOAP、WSDL 和 UDDI 的元素没有提及如何验证和保证身份或如何维护消息完整性。Web 服务的早期采用者要么保持其服务开放并可供所有人使用,要么开发专有的安全协议来填补这一空白。专有和私有方法在消息发送者和接收者之间造成了不良耦合,这在异步和松散耦合的消息节点联合中尤其令人烦恼。
While standards such as WS-ReliableMessaging, WS-Coordination, and WS-Addressing provide various mechanisms that identify the sender of a message, none of those specifications guarantee that the sender actually owns the identity it claims. The elements of the WS-I Basic ProfileXML Schema, SOAP, WSDL, and UDDImake no mention of how identity is verified and guaranteed or how message integrity is to be maintained. Early adopters of Web services either kept their services open and available to all or developed proprietary security protocols in order to fill this gap. The proprietary and private approaches created an undesirable coupling between message senders and receivers, something particularly galling in otherwise asynchronous and loosely coupled federations of message nodes.
WS-Security,也称为Web 服务安全语言,是解决这些问题的建议标准方法。在所有 WS-* 规范中,它可能在主要 Web 服务平台供应商中享有最广泛的共识。它由 Microsoft、IBM 和 Verisign 组成的联盟创建,现在是 OASIS 的一部分;它已获得 Sun、BEA、Intel、SAP、IONA、RSA Security 等公司的信任票。
WS-Security, also referred to as the Web Services Security Language, is the proposed standard means of addressing these issues. Of all the WS-* specifications, it enjoys perhaps the broadest level of consensus among the major Web services platform vendors. Created by a consortium consisting of Microsoft, IBM, and Verisign, it is now part of OASIS; it has received votes of confidence from the likes of Sun, BEA, Intel, SAP, IONA, RSA Security, and others.
WS-Security 并不提出新的安全技术,而是将 SOAP 与现有的安全技术联系起来。它提供了一种通用的、可扩展的方法,将安全令牌与 SOAP 消息关联起来,并将这些令牌传播到 SOAP 端点。它还指定了在 SOAP 消息中编码二进制安全令牌(例如数字证书和 Kerberos 票证)的标准方法。它不描述特定的固定协议,而是建立一套通用机制,用于实现任意数量的安全协议并合并任意数量的信任域、签名格式和加密技术。它尽可能包括数字证书、摘要的使用以及 PKI、Kerberos、SSL 等技术的实现,
WS-Security does not propose new security technologies, but rather it bridges SOAP and existing security technologies. It provides a generic, extensible means of associating security tokens with SOAP messages and propagating those tokens to SOAP endpoints. It also specifies the standard approach to encoding binary security tokens, such as digital certificates and Kerberos tickets, in SOAP messages. It does not describe specific fixed protocols but rather establishes a general set of mechanisms for implementing any number of security protocols and incorporating any number of trust domains, signature formats, and encryption technologies. In as much as it can include use of digital certificates, digests, and implementations of technologies such as PKI, Kerberos, SSL, and the like, WS-Security represents a way to apply familiar Internet security standbys to SOAP endpoints.
除了验证发送者身份之外,WS-Security 还可以利用 W3C XML 签名和 XML 加密标准来保护消息完整性,即在网络传输过程中保护消息免受中间人的窥探。然而,在不涉及中间参与者和第三方服务的情况下,使用 HTTPS 是保护传输中的 SOAP 消息的常见替代方法。
In addition to verification of sender identity, WS-Security may protect message integritythat is, protect it from intermediary prying eyes during network transmissionby leveraging the W3C XML Signature and XML Encryption standards. In cases in which intermediary actors and third-party services are not involved, however, the use of HTTPS is a common alternative means of protecting SOAP messages in transit.
该安全模型指定 SOAP 消息发送者提出一系列声明,例如发送者的身份、组、特权等。这些声明以签名安全令牌的形式收集。消息的接收者负责认可该声明。令牌和签名在 SOAP 标头块中携带,特别是在安全性下WS-Security 命名空间中的标头元素。如果接收方认可声明时发生错误,则该错误将是以下两种类型之一:不支持的错误,这表明端点不支持特定的令牌或加密算法;以及失败错误,这表明大多数其他错误,包括所有这些错误与无效令牌和签名相关。该规范并不强制要求始终报告故障错误,因为它们可能是攻击的结果。报告错误时,它们采用 SOAP 错误的形式,并带有规范中定义的错误代码。
The security model specifies that a SOAP message sender makes a series of claims such as the sender's identity, group, privilege, and the like. These claims are collected in the form of a signed security token. The receiver of the message is charged with endorsing the claims. The tokens and signatures are carried in a SOAP Header block, specifically under the security header element in the WS-Security namespace. If an error occurs when the receiver endorses claims, that error will be one of two types: unsupported errors, which indicate that the endpoint does not support a particular token or encryption algorithm, and failure errors, which indicate most other errors, including all those related to invalid tokens and signatures. The specification does not mandate that failure errors always be reported, as they may be the result of an attack. When errors are reported, they take the form of SOAP Faults with fault codes defined in the specification.
WS-Security 规范可以在每个共同创作供应商的网站上找到,包括 IBM 的以下位置:http://www-106.ibm.com/developerworks/library/ws-secure/。
The WS-Security specification can be found on the Web sites of each of its co-authoring vendors, including the following location at IBM: http://www-106.ibm.com/developerworks/library/ws-secure/.
还有许多其他拟议的 WS-* 规范,得到了不同程度的支持和接受。有些范围相当狭窄,例如 WS-Addressing,它定义 XML 元素来标识消息中的 Web 服务端点。该规范旨在以传输中立的方式支持通过端点管理器、代理、防火墙和网关等中介进行消息传递。本质上,WS-Addressing 是一种标准方法,用于指示消息的来源(“发件人:”)和收件人(“收件人:”)。它提供了一种将基于 SOAP 的 Web 服务插入到收件人列表解决方案中的方法。由于它提供了一种指定将回复定向到何处的方法,因此它似乎注定会成为解决 中提出的问题的标准 Web 服务方法。退货地址。
There are a number of other proposed WS-* specifications with varying degrees of support and acceptance. Some are quite narrow in scope, such as WS-Addressing, which defines XML elements to identify Web services endpoints in messages. This specification aims to support messaging through intermediaries such as endpoint managers, proxies, firewalls, and gateways in a transport-neutral manner. Essentially, WS-Addressing is a standard way to denote who a message is from ("From:") and who it is to ("To:"). It provides a means to plug SOAP-based Web services into Recipient List solutions. As it provides a means of specifying where to direct a reply, it appears destined to be the standard Web services approach to resolving the issues raised in Return Address.
Web 服务策略框架 (WS-Policy) 是一种提供有关服务的元数据的方法,它提供了用于描述 Web 服务策略的语法。这些策略包括服务需求、偏好、功能和服务质量元数据。随附的 Web 服务策略断言语言 (WS-PolicyAssertions) 指定了断言消息或服务端点支持特定策略的方法。它涉及调查 WS-Policy 声明以寻找特定的所需策略。最后,Web 服务策略附件 (WS-PolicyAttachment) 描述了这些策略标准如何适应现有的 Web 服务技术。它指定如何将策略表达式与 WSDL 类型定义和 UDDI 实体相关联,
A means of supplying metadata about services, the Web Services Policy Framework (WS-Policy) offers a syntax for describing the policies of a Web service. Such policies include service requirements, preferences, capabilities, and quality of service metadata. The accompanying Web Services Policy Assertions Language (WS-PolicyAssertions) specifies a means of asserting that a message or service endpoint supports a particular policy. It involves investigating the WS-Policy declarations in search of a specific required policy. Finally, the Web Services Policy Attachment (WS-PolicyAttachment) describes how these policy standards fit into existing Web services technologies. It specifies how to associate policy expressions with WSDL type definitions and UDDI entities, and it defines how to associate implementation-specific policies with all or part of a WSDL portType.
随着供应商竞相规范应用程序开发人员所遵循的实施实践,并在此过程中争取有价值的 Web 服务知识产权,新的 WS-* 规范正在迅速出现。明智的应用程序开发人员会关注有趣的标准,并在有用时从中剔除习惯用法,但也会记住,对于构建基于消息的应用程序来说,最重要的是模式而不是实现习惯用法。新兴的 WS-* 标准还不是实现可互操作的企业消息传递系统所必需的,如果它们成为障碍或干扰,则应大胆地忽略它们,直到它们成熟为止。
New WS-* specifications are surfacing quickly as vendors race to formalize the implementation practices that application developers are following, staking a claim to valuable Web services intellectual property in the process. Wise application developers will keep an eye on interesting standards and cull idioms from them when useful, but will also bear in mind that the pattern and not the implementation idiom is most important to crafting a message-based application. The emerging WS-* standards are not yet required for implementing interoperable enterprise messaging systems, and where they become hindrances or distractions, they should boldly be ignored until mature.
在最好的情况下,标准通过确保同一模式的不同实现可以互操作来扩展设计模式的范围。人们正在进行许多努力来通过 Web 服务标准来扩展消息传递模式;其中许多标准侧重于称为业务流程组件的工作流组件的组成和行为。BPEL、WSCI 和 WS-* 规范等标准解决了本书中描述的许多问题,并用这种模式语言实现了一些模式。
At their best, standards extend the reach of design patterns by ensuring that different implementations of the same pattern are interoperable. Many efforts are underway to extend messaging patterns through Web services standards; many of these standards focus on the composition and behavior of workflow components called business process components. Standards such as BPEL, WSCI, and the WS-* specifications tackle many of the problems described in this book and implement several of the patterns in this pattern language.
尽管如此,新兴标准的故事有时会发生冲突并且可能令人困惑。他们当然还没有准备好迎接黄金时段。应用模式时,应用程序开发人员应避免陷入标准困境,而应专注于手头的特定用例。看看某些标准是如何解决问题的,如果有意义或者在实施过程中是否有帮助,就采用这些战术性、惯用的模式实现,无论该方法是否跨供应商产品标准化。开发人员还可以向标准组织提供反馈,以提出质疑、批评或以其他方式确保标准变得实用,而不是学术或供应商的练习。随着标准的成熟,
Still, the emerging standards stories are occasionally conflicting and can be confusing. They're certainly not all ready for prime time. When applying a pattern, an application developer should avoid getting bogged down by standards and stay focused instead on the particular use cases at hand. Take a look at how certain standards are approaching a problem, and adopt those tactical, idiomatic implementations of a pattern if it makes sense or if it's helpful during implementationregardless of whether the approach is standardized across vendor products. Developers can also provide feedback to standards organizations to challenge, criticize, and otherwise ensure that standards become practically useful and not academic or vendor exercises. As standards mature, architects are wise to consider how their use might extend enterprise integration solutions to broader, less risky, less costly, and more powerful levels of sophistication.
[亚历山大]
模式语言:城镇、建筑、建筑, Christopher Alexander、Sara Ishikawa 和 Murray Silverstein;牛津大学出版社 1977 年,ISBN 0195019199
可能是所有与模式相关的著作中被引用最多的书。实际上,我使用了亚历山大的一些图案来为室内建筑课程设计一座海滨别墅。有趣的是,亚历山大将建筑和设计剖析成一组可组合结构的愿望可能源于他对数学的研究。他的论文发表为“关于形式综合的注释”,引用了他用 IBM 7090 汇编代码开发的一组程序。所以,
[Alexander]
A Pattern Language: Towns, Buildings, Construction, Christopher Alexander, Sara Ishikawa, & Murray Silverstein; Oxford University Press 1977, ISBN 0195019199
Probably the most quoted book in any work related to patterns. I actually used a number of Alexander's patterns to design a beach house for an interior architecture class. It is interesting to note that Alexander's desire to dissect architecture and design into a set of composable constructs may stem from his study of mathematics. His thesis, published as "Notes on the synthesis of form," references a set of programs that he developed in IBM 7090 assembly code. So, the tremendous success of Alexander's patterns in the software community is not quite coincidental.
[Alpert]
设计模式 Smalltalk Companion, Sherman Alpert、Kyle Brown 和 Bobby Woolf;Addison-Wesley 1998,ISBN 0201184621
再看一下设计模式材料[GoF],这次是针对使用具有公共类库并在具有垃圾收集功能的虚拟机上运行的编程环境的开发人员。当时,这是 Smalltalk,但许多见解也适用于 Java 和 .NET/C#。
[Alpert]
The Design Patterns Smalltalk Companion, Sherman Alpert, Kyle Brown, & Bobby Woolf; Addison-Wesley 1998, ISBN 0201184621
A second look at the Design Patterns material [GoF], this time for developers using a programming environment that has a common class library and runs on a virtual machine with garbage collection. At the time, that was Smalltalk, but many of the insights also apply to Java and .NET/C#.
[Box]
Essential .NET,第 1 卷:公共语言运行时,Don Box;Addison-Wesley 2002,ISBN 0201734117
关于 CLR 内部工作原理的了解超乎您的想象。
[Box]
Essential .NET, Volume 1: The Common Language Runtime, Don Box; Addison-Wesley 2002, ISBN 0201734117
More than you ever wanted to know about the inner workings of the CLR.
[BPEL4WS]
Web 服务业务流程执行语言,版本 1.0,BEA、IBM、Microsoft;2002 年 7 月 31 日,http://www.ibm.com/developerworks/webservices/library/ws-bpel1/
BPEL4WS 1.0 规范。
[BPEL4WS]
Business Process Execution Language for Web Services, Version 1.0, BEA, IBM, Microsoft; July 31, 2002, http://www.ibm.com/developerworks/webservices/library/ws-bpel1/
The BPEL4WS 1.0 specification.
[CoreJ2EE]
核心 J2EE 模式:最佳实践和设计策略(第二版),Deepak Alur、John Crupi 和 Dan Malks;普伦蒂斯·霍尔 PTR 2003,ISBN 0131422464
一本关于 Java 企业应用程序架构模式的非常好的书。
[CoreJ2EE]
Core J2EE Patterns: Best Practices and Design Strategies (2nd edition), Deepak Alur, John Crupi, & Dan Malks; Prentice Hall PTR 2003, ISBN 0131422464
A very good book on enterprise application architecture patterns for Java.
[CSP]
“通信顺序过程”, CAR Hoare;ACM 通讯,1978
ACM 在线图书馆需要访问才能查看本文的全文版本。
[CSP]
"Communicating Sequential Processes," C. A. R. Hoare; Communications of the ACM, 1978
ACM online library access is required to see the full-text version of this article.
[Dickman]
使用 MSMQ 设计应用程序,Alan Dickman;Addison-Wesley 1998,ISBN 0201325810
这本书包含一个关于“消息传递问题的解决方案”的精彩章节,涉及相关性、事件驱动的消费者以及对象序列化和反序列化。不幸的是,本书的年代久远意味着所有示例都使用带有 COM 或 C++ 的 Visual Basic。
[Dickman]
Designing Applications with MSMQ, Alan Dickman; Addison-Wesley 1998, ISBN 0201325810
The book contains a great chapter on "Solutions to Messaging Problems" that deals with correlation, event-driven consumers and object serialization, and deserialization. Unfortunately, the age of the book means that all examples use Visual Basic with COM or C++.
[Douglass]
实时设计模式, Bruce Powel Douglass;Addison-Wesley 2003,ISBN 0201699567
本书证明了模式跨领域的可移植性。事实证明,道格拉斯的一些可靠性模式在企业消息传递环境中非常有用。
[Douglass]
Real-Time Design Patterns, Bruce Powel Douglass; Addison-Wesley 2003, ISBN 0201699567
This book proves the transportability of patterns across domains. Some of Douglass's reliability patterns prove very useful in the context of enterprise messaging.
[EAA]
企业应用程序架构模式, Martin Fowler;Addison-Wesley 2003,ISBN 0321127420
迄今为止关于应用程序架构模式的最全面的书籍。尽管它涵盖了 51 种模式,但它读起来既简单又有趣,同时又不牺牲技术准确性。
[EAA]
Patterns of Enterprise Application Architecture, Martin Fowler; Addison-Wesley 2003, ISBN 0321127420
The most comprehensive book yet on application architecture patterns. Even though it covers 51 patterns, it is an easy and interesting read while never sacrificing technical accuracy.
[EJB 2.0]
Enterprise JavaBeans 规范,版本 2.0,Sun Microsystems;2001 年 8 月 14 日,http://java.sun.com/products/ejb/docs.html
EJB 2.0 规范。
[EJB 2.0]
Enterprise JavaBeans Specification, Version 2.0, Sun Microsystems; August 14, 2001, http://java.sun.com/products/ejb/docs.html
The EJB 2.0 specification.
[Garlan]
软件架构:新兴学科的视角, Mary Shaw 和 David Garlan;Prentice Hall 1996,ISBN 0131829572
这本书包含关于建筑风格的精彩章节,包括>管道和过滤器。
[Garlan]
Software Architecture: Perspectives on an Emerging Discipline, Mary Shaw & David Garlan; Prentice Hall 1996, ISBN 0131829572
The book contains a great chapter on architectural styles, including >Pipes and Filters.
[GoF]
设计模式:可重用面向对象软件的元素, Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides;Addison-Wesley 1995,ISBN 0201633612
无疑是所有模式著作中被引用次数第二多的书。
[GoF]
Design Patterns: Elements of Reusable Object-Oriented Software, Erich Gamma, Richard Helm, Ralph Johnson, & John Vlissides; Addison-Wesley 1995, ISBN 0201633612
Surely the second most quoted book in any work on patterns.
[Graham]
使用 Java 构建 Web 服务:理解 XML、SOAP 和 UDDI,Steve Graham、Simon Simeonov、Toufic Boubez、Glen Daniels、Doug Davis、Yuichi Nakamura 和 Ryo Nyeama;SAMS Publishing 2002,ISBN 0672321815
一本关于 Java 和 Web 服务如何结合在一起的非常好的书。
[Graham]
Building Web Services with Java: Making Sense of XML, SOAP and UDDI, Steve Graham, Simon Simeonov, Toufic Boubez, Glen Daniels, Doug Davis, Yuichi Nakamura, & Ryo Nyeama; SAMS Publishing 2002, ISBN 0672321815
A very good book on how Java and Web services come together.
[Hapner]
Java 消息服务 API 教程和参考, Mark Hapner、Rich Burridge、Rahul Sharma、Joseph Fialli 和 Kim Haase;Addison-Wesley 2002,ISBN 0201784726
JMS 如何工作,来自编写该规范的作者。
[Hapner]
Java Messaging Service API Tutorial and Reference, Mark Hapner, Rich Burridge, Rahul Sharma, Joseph Fialli, & Kim Haase; Addison-Wesley 2002, ISBN 0201784726
How JMS works, from the authors who wrote the specification.
[Hohmann]
超越软件架构:创建和维持获胜解决方案, Luke Hohmann;Addison-Wesley 2003,ISBN 0201775948
Luke 提醒我们有多少架构决策并非仅由技术驱动,而是由业务决策、许可方案和许多其他外部因素驱动。
[Hohmann]
Beyond Software Architecture: Creating and Sustaining Winning Solutions, Luke Hohmann; Addison-Wesley 2003, ISBN 0201775948
Luke reminds us how many architectural decisions are not driven by technology alone but by business decisions, licensing schemes, and a host of other external factors.
[JMS]
Java 消息服务 (JMS),Sun Microsystems;20012003,http://java.sun.com/products/jms/
Java 消息服务 API,Java 2 企业版 (J2EE) 平台的一部分。
[JMS]
Java Message Service (JMS), Sun Microsystems; 20012003, http://java.sun.com/products/jms/
The Java Message Service API, part of the Java 2, Enterprise Edition (J2EE) platform.
[JMS 1.1]
Java 消息服务(Sun Java 消息服务 1.1 规范),Sun Microsystems;2002 年 4 月 12 日,http://java.sun.com/products/jms/docs.html
JMS 1.1 规范。
[JMS 1.1]
Java Message Service (the Sun Java Message Service 1.1 Specification), Sun Microsystems; April 12, 2002, http://java.sun.com/products/jms/docs.html
The JMS 1.1 specification.
[JTA]
Java 事务 API (JTA),Sun Microsystems;20012003,http://java.sun.com/products/jta/
Java 事务 API,Java 2 企业版 (J2EE) 平台的一部分。
[JTA]
Java Transaction API (JTA), Sun Microsystems; 20012003, http://java.sun.com/products/jta/
The Java Transaction API, part of the Java 2, Enterprise Edition (J2EE) platform.
[Kahn]
“并行编程简单语言的语义”, G. Kahn,信息处理 74:Proc。IFIP 第 74 届大会,北荷兰出版公司,1974 年
[Kahn]
"The Semantics of a Simple Language for Parallel Programming," G. Kahn, Information Processing 74: Proc. IFIP Congress 74, North-Holland Publishing Co., 1974
[Kaye]
松散耦合:Web 服务的缺失部分, Doug Kaye;RDS Press 2003,ISBN 1881378241
对 Web 服务的全新审视。我们不用费力地阅读 API,而是以技术中立、无行话的方式了解面向服务的架构中工作的核心原理。对于渴望进行 SOAP 调用的开发人员来说,这本书可能太高级了,但对于必须向非技术人员解释这些概念的技术经理和架构师来说,它是理想的选择。
[Kaye]
Loosely Coupled: The Missing Pieces of Web Services, Doug Kaye; RDS Press 2003, ISBN 1881378241
A refreshing look at Web services. Instead of wading through APIs, we get to read about the core principles at work in service-oriented architectures in a technology-neutral, jargon-free way. This book is likely too high-level for developers itching to make that SOAP call, but it is ideal for technical managers and architects who have to explain these concepts to non-techies.
[肯特]
数据与现实,威廉·肯特;1stBooks 2000,ISBN 1585009709
一本经典书籍(原版为 1978 年),它告诉我们为什么在计算机系统内部对现实进行建模如此困难。
[Kent]
Data and Reality, William Kent; 1stBooks 2000, ISBN 1585009709
A classic book (the original edition is from 1978) that tells us why modeling reality inside a computer system is so hard.
[Lewis]
MSMQ 和 MQSeries 的高级消息传递应用程序, Rhys Lewis;阙2000年,ISBN 078972023X
[Lewis]
Advanced Messaging Applications with MSMQ and MQSeries, Rhys Lewis; Que 2000, ISBN 078972023X
[Leyman]
生产工作流程:概念和技术, Frank Leyman 和 Dieter Roller;普伦蒂斯·霍尔 PTR 1999,ISBN 0130217530
[Leyman]
Production Workflow: Concepts and Techniques, Frank Leyman & Dieter Roller; Prentice-Hall PTR 1999, ISBN 0130217530
[MDMSG]
多目标消息传递,微软;2003 年 2 月,http://msdn.microsoft.com/library/en-us/msmq/msmq_about_messages_8aqv.asp
讨论 MSMQ 3.0 中用于将消息发送到多个目标的新功能。
[MDMSG]
Multiple-Destination Messaging, Microsoft; February 2003, http://msdn.microsoft.com/library/en-us/msmq/msmq_about_messages_8aqv.asp
Discusses the new feature in MSMQ 3.0 for sending messages to more than one destination.
[MicroWorkflow]
“微工作流:支持组合式面向对象软件开发的工作流架构”, Dragos Manolescu;伊利诺伊大学 2000 年,http://micro-workflow.com/PhDThesis/phdthesis.pdf
[MicroWorkflow]
"Micro-Workflow: A Workflow Architecture Supporting Compositional Object-Oriented Software Development," Dragos Manolescu; University of Illinois 2000, http://micro-workflow.com/PhDThesis/phdthesis.pdf
[Monroe]
“风格化架构、设计模式和对象”, Robert T. Monroe、Drew Kompanek、Ralph Melton 和 David Garlan;1996,http://www-2.cs.cmu.edu/afs/cs/project/compose/ftp/pdf/ObjPatternsArch-ieee97.pdf
[Monroe]
"Stylized Architecture, Design Patterns, and Objects," Robert T. Monroe, Drew Kompanek, Ralph Melton, & David Garlan; 1996, http://www-2.cs.cmu.edu/afs/cs/project/compose/ftp/pdf/ObjPatternsArch-ieee97.pdf
[Monson-Haefel]
Java 消息服务, Richard Monson-Haefel 和 David A. Chappell;O'Reilly 2001,ISBN 0596000685
也许是最著名的 JMS 书籍。
[Monson-Haefel]
Java Message Service, Richard Monson-Haefel & David A. Chappell; O'Reilly 2001, ISBN 0596000685
Perhaps the best-known JMS book.
[MQSeries]
WebSphere MQ(以前称为 MQSeries),IBM;http://www.software.ibm.com/ts/mqseries最古老、最著名的消息传递和集成产品之一。
[MQSeries]
WebSphere MQ (formerly MQSeries), IBM; http://www.software.ibm.com/ts/mqseries
One of the oldest and best known messaging and integration products.
[MSMQ]
微软消息队列(MSMQ),微软;http://www.microsoft.com/windows2000/technologies/communications/msmq/
Windows 2000、Windows XP 和 Windows Server 2003 中内置的消息传递产品。
[MSMQ]
Microsoft Message Queuing (MSMQ), Microsoft; http://www.microsoft.com/windows2000/technologies/communications/msmq/
The messaging product built into Windows 2000, Windows XP, and Windows Server 2003.
[PatternForms]
模式形式,Wiki-Wiki-Web,Cunningham & Cunningham;最后编辑于 2002 年 8 月 26 日,http://c2.com/cgi/wiki?PatternForms 常用模式形式及其差异的
列表。
[PatternForms]
Pattern Forms, Wiki-Wiki-Web, Cunningham & Cunningham; last edited on August 26, 2002, http://c2.com/cgi/wiki?PatternForms
A list of commonly used pattern forms and their differences.
[PLoPD1]
程序设计的模式语言,James Coplien 和 Douglas Schmidt(编辑);Addison-Wesley 1995,ISBN 0201607344
第一届 PLoP 会议的会议记录。这些会议记录包含许多论文,这些论文构成了后来书籍的基础,例如 [POSA]。本书包含 Frank Buschmann 和 Regine Meunier 的“模式系统”、Regine Meunier 的“管道和过滤器架构”以及 Diane Mularz 的“基于模式的集成架构”。
[PLoPD1]
Pattern Languages of Program Design, James Coplien & Douglas Schmidt (Editors); Addison-Wesley 1995, ISBN 0201607344
The proceedings from the first PLoP conference. These proceedings contain a lot of papers that formed the basis for later books, such as [POSA]. This volume contains Frank Buschmann's and Regine Meunier's "A System of Patterns," Regine Meunier's "The Pipes and Filters Architecture," and Diane Mularz's "Pattern-Based Integration Architectures."
[PLoPD3]
程序设计模式语言 3,Robert Martin、Dirk Riehle 和 Frank Buschmann(编辑);Addison-Wesley 1998,ISBN 0201310112。PLoP会议(PLoP、EuroPLoP 等;请参阅http://hillside.net/conferencesnavigation.htm
)的第三本书包含成为 [POSA2] 基础的模式:Acceptor 和 Connector 、异步完成令牌和双重检查锁定。它还包含本书作者的两种模式:空对象和类型对象。
[PLoPD3]
Pattern Languages of Program Design 3, Robert Martin, Dirk Riehle, & Frank Buschmann (Editors); Addison-Wesley 1998, ISBN 0201310112.
The third book from the PLoP conferences (PLoP, EuroPLoP, etc.; see http://hillside.net/conferencesnavigation.htm) contains patterns that became the basis for [POSA2]: Acceptor and Connector, Asynchronous Completion Token, and Double-Checked Locking. It also contains two patterns from the authors of this book: Null Object and Type Object.
[POSA]
面向模式的软件架构, Frank Buschmann、Regine Meunier、Hans Rohnert、Peter Sommerlad 和 Michael Stal;Wiley 1996,ISBN 0471958697
一本关于架构和设计模式的好书。
[POSA]
Pattern-Oriented Software Architecture, Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad, & Michael Stal; Wiley 1996, ISBN 0471958697
A great book on architecture and design patterns.
[POSA2]
面向模式的软件架构,卷。2、道格拉斯·施密特、迈克尔·斯塔尔、汉斯·罗内特和弗兰克·布施曼;Wiley 2000,ISBN 0471606952
更多模式侧重于分布式系统和并发问题。
[POSA2]
Pattern-Oriented Software Architecture, Vol. 2, Douglas Schmidt, Michael Stal, Hans Rohnert, & Frank Buschmann; Wiley 2000, ISBN 0471606952
More patterns focused on distributed systems and concurrency issues.
[Sharp]
工作流程建模:流程改进和应用程序开发工具,Alec Sharp 和 Patrick McDermott;Artech House 2001,ISBN 1580530214
本书重点关注工作流的建模方面,对于分析师和业务架构师来说是一本有趣的读物。
[Sharp]
Workflow Modeling: Tools for Process Improvement and Application Development, Alec Sharp & Patrick McDermott; Artech House 2001, ISBN 1580530214
This book focuses on the modeling aspect of workflowan interesting read for analysts and business architects alike.
[SOAP 1.1]
W3C 简单对象访问协议 (SOAP) 1.1 规范,万维网联盟;W3C 注释,2000 年 5 月 8 日,http://www.w3.org/TR/SOAP/
SOAP 1.1 规范。
[SOAP 1.1]
W3C Simple Object Access Protocol (SOAP) 1.1 Specification, World Wide Web Consortium; W3C Note, May 8, 2000, http://www.w3.org/TR/SOAP/
The SOAP 1.1 specification.
[SOAP 1.2 第 2 部分] SOAP 版本 1.2,第 2 部分:附件,万维网联盟;W3C 建议,2003 年 6 月 24 日,http://www.w3.org/TR/soap12-part2/
SOAP 1.2 规范的“额外部分”。
[SOAP 1.2 Part 2] SOAP Version 1.2, Part 2: Adjuncts, World Wide Web Consortium; W3C Recommendation, June 24, 2003, http://www.w3.org/TR/soap12-part2/
The "extra parts" of the SOAP 1.2 specification.
[Stevens]
TCP/IP 图解,第 1 卷:协议,W. Richard Stevens;艾迪生·韦斯利 1994 年,ISBN 0201633469
[Stevens]
TCP/IP Illustrated, Volume 1: The Protocols, W. Richard Stevens; Addison-Wesley 1994, ISBN 0201633469
[SysMsg]
“System.Messaging 命名空间”, .NET Framework,版本 1.1,Microsoft;http://msdn.microsoft.com/library/en-us/cpref/html/cpref_start.asp
[SysMsg]
"System.Messaging namespace," .NET Framework, version 1.1, Microsoft; http://msdn.microsoft.com/library/en-us/cpref/html/cpref_start.asp
[Tennison]
XSLT 和 XPath on the Edge,Jeni Tennison;约翰·威利父子公司 2001 年,ISBN 0764547763
[Tennison]
XSLT and XPath on the Edge, Jeni Tennison; John Wiley & Sons 2001, ISBN 0764547763
[UML]
UML Distilled:标准对象建模语言简要指南(第 3 版),Martin Fowler;Addison-Wesley 2003,ISBN 0321193687
学习 UML 图的优秀资源,它们应该是什么样子以及它们的含义。
[UML]
UML Distilled: A Brief Guide to the Standard Object Modeling Language (3rd edition), Martin Fowler; Addison-Wesley 2003, ISBN 0321193687
A excellent source for learning about UML diagramswhat they're supposed to look like and what they mean.
[UMLEAI]
“企业应用程序集成的 UML 配置文件”,对象管理组;2002 年,http://www.omg.org/technology/documents/modeling_spec_catalog.htm
[UMLEAI]
"UML Profile for Enterprise Application Integration," Object Management Group; 2002, http://www.omg.org/technology/documents/modeling_spec_catalog.htm
[Wright]
TCP/IP 图解,第 2 卷:实施,Gary R. Wright 和 W. Richard Stevens;艾迪生·韦斯利 1995 年,ISBN 020163354X
[Wright]
TCP/IP Illustrated, Volume 2: The Implementation, Gary R. Wright & W. Richard Stevens; Addison-Wesley 1995, ISBN 020163354X
[WSAUS]
“Web 服务架构使用场景”,万维网联盟;W3C 工作草案,2003 年 5 月 14 日,http://www.w3.org/TR/ws-arch-scenarios/
W3C 关于如何使用 Web 服务以及规范需要满足哪些要求的最新想法。
[WSAUS]
"Web Services Architecture Usage Scenarios," World Wide Web Consortium; W3C Working Draft, May 14, 2003, http://www.w3.org/TR/ws-arch-scenarios/
The W3C's latest thinking on how Web services will be used and what requirements the specifications need to fulfill.
[WSDL 1.1] Web 服务描述语言 (WSDL) 1.1,万维网联盟;W3C 注释,2001 年 3 月 15 日,http://www.w3.org/TR/wsdl
WSDL 1.1 规范。
[WSDL 1.1] Web Services Description Language (WSDL) 1.1, World Wide Web Consortium; W3C Note March 15, 2001, http://www.w3.org/TR/wsdl
The WSDL 1.1 specification.
[WSFL]
Web 服务流语言 (WSFL) 1.0,IBM;2001 年 5 月,http://www-3.ibm.com/software/solutions/webservices/pdf/WSFL.pdf WSFL 1.0 规范。
[WSFL]
Web Services Flow Language (WSFL) 1.0, IBM; May 2001, http://www-3.ibm.com/software/solutions/webservices/pdf/WSFL.pdf The WSFL 1.0 specification.
[WSMQ]
使用 Java 的 WebSphere MQ(第二版),IBM;2002 年 10 月,http://publibfp.boulder.ibm.com/epubs/pdf/csqzaw11.pdf
使用 IBM 的 WebSphere MQ 消息传递产品 [MQSeries] 的 Java 程序员的开发人员指南。
[WSMQ]
WebSphere MQ Using Java (2nd edition), IBM; October 2002, http://publibfp.boulder.ibm.com/epubs/pdf/csqzaw11.pdf
The developer's guide for Java programmers using IBM's WebSphere MQ messaging product [MQSeries].
[XML 1.0]
可扩展标记语言 (XML) 1.0(第 2 版),万维网联盟;W3C 建议,2000 年 10 月 6 日,http://www.w3.org/TR/REC-xml
XML 1.0 规范。
[XML 1.0]
Extensible Markup Language (XML) 1.0 (2nd edition), World Wide Web Consortium; W3C Recommendation, October 6, 2000, http://www.w3.org/TR/REC-xml
The XML 1.0 specification.
[XSLT 1.0]
XSL 转换 (XSLT) 1.0 版,万维网联盟;W3C 建议,1999 年11 月16 日, http://www.w3.org/TR/xslt XSLT
1.0 规范。
[XSLT 1.0]
XSL Transformations (XSLT) Version 1.0, World Wide Web Consortium; W3C Recommendation, November 16, 1999, http://www.w3.org/TR/xslt
The XSLT 1.0 specification.
[Waldo]
“分布式计算说明”(技术报告SMLI TR-94-29),Jim Waldo、Geoff Wyant、Ann Wollrath 和 Sam Kendall;Sun Microsystems 实验室,1994 年 11 月,http://citeseer.nj.nec.com/waldo94note.html
[Waldo]
"A Note on Distributed Computing" (Technical Report SMLI TR-94-29), Jim Waldo, Geoff Wyant, Ann Wollrath, & Sam Kendall; Sun Microsystems Laboratories, November 1994, http://citeseer.nj.nec.com/waldo94note.html
[Zahavi]
企业应用程序与 CORBA 集成,Ron Zahavi;约翰·威利父子公司 1999 年,ISBN 0471327204
[Zahavi]
Enterprise Application Integration with CORBA, Ron Zahavi; John Wiley & Sons 1999, ISBN 0471327204
聚合器 我们如何组合各个但相关的消息的结果,以便可以将它们作为一个整体进行处理? Aggregator How do we combine the results of individual but related messages so that they can be processed as a whole? |
规范数据模型 在集成使用不同数据格式的应用程序时,如何最大限度地减少依赖性?
Canonical Data Model How can you minimize dependencies when integrating applications that use different data formats?
通道适配器如何 Channel Adapter How can you connect an application to the messaging system so that it can send and receive messages? |
通道清除器 如何防止通道上的剩余消息干扰测试或正在运行的系统? Channel Purger How can you keep leftover messages on a channel from disturbing tests or running systems? |
索赔检查如何在不牺牲信息内容的情况下减少整个系统发送的消息的数据量? Claim Check How can we reduce the data volume of message sent across the system without sacrificing information content? |
命令消息 如何 Command Message How can messaging be used to invoke a procedure in another application? |
竞争 消费者 Competing Consumers How can a messaging client process multiple messages concurrently? |
组合消息处理器 Composed Message Processor How can you maintain the overall message flow when processing a message consisting of multiple elements, each of which may require different processing? |
内容丰富器 如果消息发起者没有提供所有必需的数据项,我们如何 Content Enricher How do we communicate with another system if the message originator does not have all the required data items available? |
内容过滤器当您只对少数数据项感兴趣时,如何简化对大消息的处理? Content Filter How do you simplify dealing with a large message when you are interested only in a few data items? |
基于内容的路由器我们如何处理单个逻辑功能的实现分布在多个物理系统上的情况? Content-Based Router How do we handle a situation in which the implementation of a single logical function is spread across multiple physical systems? |
控制总线 我们如何有效地管理分布在多个平台和广泛地理区域的消息传递系统? Control Bus How can we effectively administer a messaging system that is distributed across multiple platforms and a wide geographic area? |
相关标识符 收到回复的请求者如何知道这是哪个请求的回复? Correlation Identifier How does a requestor that has received a reply know which request this is the reply for? |
Datatype Channel How can the application send a data item such that the receiver will know how to process it? |
Dead Letter Channel What will the messaging system do with a message it cannot deliver? |
迂回 如何通过中间步骤路由消息以执行验证、测试或调试功能? Detour How can you route a message through intermediate steps to perform validation, testing, or debugging functions? |
文档消息 如何 Document Message How can messaging be used to transfer data between applications? |
持久订阅者 订阅者 如何 Durable Subscriber How can a subscriber avoid missing messages while it's not listening for them? |
动态路由器如何避免路由器对所有可能目的地的依赖,同时保持其效率? Dynamic Router How can you avoid the dependency of the router on all possible destinations while maintaining its efficiency? |
信封包装器 Envelope Wrapper How can existing systems participate in a messaging exchange that places specific requirements, such as message header fields or encryption, on the message format? |
事件消息 如何 Event Message How can messaging be used to transmit events from one application to another? |
事件驱动的使用者应用程序 如何 Event-Driven Consumer How can an application automatically consume messages as they become available? |
文件传输如何 File Transfer How can I integrate multiple applications so that they work together and can exchange information? |
格式指示器 如何 设计消息的数据格式以适应未来可能的变化?
Format Indicator How can a message's data format be designed to allow for possible future changes?
保证传送发送者如何确保即使消息传递系统出现故障也能传送消息? Guaranteed Delivery How can the sender make sure that a message will be delivered even if the messaging system fails? |
幂等接收者 消息接收者 如何
Idempotent Receiver How can a message receiver deal with duplicate messages?
Invalid Message Channel How can a messaging receiver gracefully handle receiving a message that makes no sense? |
消息代理 如何将消息的目的地与发送者分离并保持对消息流的集中控制? Message Broker How can you decouple the destination of a message from the sender and maintain central control over the flow of messages? |
消息总线 哪种架构能够使单独的应用程序以解耦的方式协同工作,以便可以轻松添加或删除应用程序而不影响其他应用程序? Message Bus What architecture enables separate applications to work together but in a decoupled fashion such that applications can be easily added or removed without affecting the others? |
消息通道 一个应用程序 如何 Message Channel How does one application communicate with another using messaging? |
消息调度 程序 Message Dispatcher How can multiple consumers on a single channel coordinate their message processing? |
消息端点 应用程序如何连接到消息通道来发送和接收消息? Message Endpoint How does an application connect to a messaging channel to send and receive Messages? |
消息过期 发件人 如何 Message Expiration How can a sender indicate when a message should be considered stale and thus shouldn't be processed? |
消息过滤器 组件如何避免接收不感兴趣的消息? Message Filter How can a component avoid receiving uninteresting messages? |
消息历史记录 我们 如何
Message History How can we effectively analyze and debug the flow of messages in a loosely coupled system?
消息路由器 如何解耦各个处理步骤,以便消息可以根据一组条件传递到不同的过滤器? Message Router How can you decouple individual processing steps so that messages can be passed to different filters depending on a set of conditions? |
消息 序列 Message Sequence How can messaging transmit an arbitrarily large amount of data? |
消息存储 我们如何在不影响消息传递系统的松散耦合和瞬态性质的情况下报告消息信息? Message Store How can we report against message information without disturbing the loosely coupled and transient nature of a messaging system? |
Message Translator How can systems using different data formats communicate with each other using messaging? |
Message How can two applications connected by a message channel exchange a piece of information? |
消息传递桥 如何连接多个消息传递系统,以便一个系统上可用的消息在其他系统上也可用? Messaging Bridge How can multiple messaging systems be connected so that messages available on one are also available on the others? |
消息传递网关 如何 Messaging Gateway How do you encapsulate access to the messaging system from the rest of the application? |
消息传递映射器 如何在域对象和消息传递基础结构之间移动数据,同时保持两者相互独立?
Messaging Mapper How do you move data between domain objects and the messaging infrastructure while keeping the two independent of each other?
消息传递 如何 Messaging How can I integrate multiple applications so that they work together and can exchange information? |
规范化器 如何 处理语义上相同但以不同格式到达的消息? Normalizer How do you process messages that are semantically equivalent but arrive in a different format? |
管道和过滤器 我们 如何 Pipes and Filters How can we perform complex processing on a message while maintaining independence and flexibility? |
点对点通道 呼叫者 如何 Point-to-Point Channel How can the caller be sure that exactly one receiver will receive the document or perform the call? |
轮询 消费者 Polling Consumer How can an application consume a message when the application is ready? |
流程管理器 当所需的步骤在设计时可能未知并且可能不连续时,我们 如何 Process Manager How do we route a message through multiple processing steps when the required steps may not be known at design time and may not be sequential? |
Publish-Subscribe Channel How can the sender broadcast an event to all interested receivers? |
收件人列表我们如何将邮件路由到动态收件人列表? Recipient List How do we route a message to a dynamic list of recipients? |
远程过程调用如何 Remote Procedure Invocation How can I integrate multiple applications so that they work together and can exchange information? |
请求-回复 当应用程序发送消息时,如何从接收者那里得到响应? Request-Reply When an application sends a message, how can it get a response from the receiver? |
重排序器 我们 如何 Resequencer How can we get a stream of related but out-of-sequence messages back into the correct order? |
回复地址 回复者 如何 Return Address How does a replier know where to send the reply? |
路由表当设计时步骤顺序未知并且每条消息的步骤顺序可能不同时,我们如何通过一系列处理步骤连续路由消息? Routing Slip How do we route a message consecutively through a series of processing steps when the sequence of steps is not known at design time and may vary for each message? |
分散-收集当一条消息必须发送给多个收件人且每个收件人都可以发送回复时,如何维护整个消息流?
Scatter-Gather How do you maintain the overall message flow when a message must be sent to multiple recipients, each of which may send a reply?
选择性消费者消息消费者 如何 Selective Consumer How can a message consumer select which messages it wishes to receive? |
服务激活器 应用 程序如何设计可通过各种消息传递技术和非消息传递技术调用的服务? Service Activator How can an application design a service to be invoked both via various messaging technologies and via non-messaging techniques? |
共享数据库 如何 Shared Database How can I integrate multiple applications so that they work together and can exchange information? |
智能代理 如何跟踪向请求者指定的返回地址发布回复消息的服务上的消息? Smart Proxy How can you track messages on a service that publishes reply messages to the Return Address specified by the requestor? |
如果消息包含多个元素,并且每个元素可能必须以不同的方式处理,那么我们如何处理该消息? Splitter How can we process a message if it contains multiple elements, each of which may have to be processed in a different way? |
测试消息 如果组件正在主动处理消息,但由于内部故障而导致传出消息出现乱码,会发生 什么 Test Message What happens if a component is actively processing messages but garbles outgoing messages due to an internal fault? |
事务客户端 客户端如何控制其与消息系统的事务? Transactional Client How can a client control its transactions with the messaging system? |
Wire Tap 如何检查在点对点通道上传输的消息? Wire Tap How do you inspect messages that travel on a Point-to-Point Channel? |